// Grains of sand falling in a hourglass.
//
// by Koepel, 26 May 2024, Public Domain.
//
// I'm not entirely happy with the code,
// but at least I got the timing and the steps right.
//
// Calculate the ongoing time:
// A ledstrip of 16 leds with 8 grains of sand.
// The first grain falls 8 places.
// The last grain falls 1 place.
// 1+2+3+4+5+6+7+8 = 36 steps.
#include <FastLED.h>
#define NUM_LEDS 16
#define DATA_PIN 6
CRGB leds[NUM_LEDS];
const CRGB idleColor = CRGB::Khaki;
const CRGB activeColor = CRGB::OrangeRed;
const CRGB finishedColor = CRGB::Plum;
const int startButtonPin = 12;
const int resetButtonPin = 11;
int grains_top; // the number of grains of sand at the top
int grains_bottom; // the number of grains of sand at the bottom
const int interval = 300; // the delay for each step.
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println("⌛ Grains of sand falling in a hourglass ⌛");
Serial.println("Press the start button.");
pinMode(startButtonPin, INPUT_PULLUP);
pinMode(resetButtonPin, INPUT_PULLUP);
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
// FastLED.setBrightness(200); // Adjust the brightness to 200
/*
// Test the function showSand().
Serial.print("Testing the showSand() function...");
showSand(8,0,-1);
delay(1000);
showSand(0,8,-1);
delay(1000);
showSand(0,0,0);
delay(1000);
showSand(0,0,15);
delay(1000);
Serial.println(" done.");
*/
// set initial values
grains_top = 8;
grains_bottom = 0;
showSand(grains_top, grains_bottom, -1, idleColor);
}
void loop()
{
int start = digitalRead(startButtonPin);
int reset = digitalRead(resetButtonPin);
if(start == LOW and grains_top > 0)
{
Serial.print("The hourglass is running : ");
int steps = 0;
int unsigned long t1 = millis();
// There are 8 grains of sand that have to fall.
for(int i=0; i<8; i++)
{
// The grain of sand is taken from index 7,
// and then it starts to fall to index 8.
// At the same moment, the number of grains in the top
// is one grain lower.
int falling = 8;
// Reduce the grains of sand at the top.
grains_top--;
// let the grain fall.
while(falling < NUM_LEDS - grains_bottom)
{
showSand( grains_top, grains_bottom, falling, activeColor);
delay(interval);
falling++;
steps++;
Serial.print("█"); // UTF-8 character "Full Block"
}
// One grain of sand was added to the bottom.
grains_bottom++;
}
unsigned long t2 = millis();
unsigned long elapsedMillis = t2 - t1;
// Update the leds once more to show the new color
showSand( grains_top, grains_bottom, -1, finishedColor);
Serial.println();
Serial.println("Theoretical number of steps: 36");
Serial.print("Measured number of steps : ");
Serial.println(steps);
Serial.print("Theoretical time : ");
Serial.print(interval * 36);
Serial.println(" ms");
Serial.print("Measured time : ");
Serial.print(elapsedMillis);
Serial.println(" ms");
}
if(reset == LOW)
{
// set initial values
grains_top = 8;
grains_bottom = 0;
showSand(grains_top, grains_bottom, -1, idleColor);
}
delay(10);
}
// Show the leds at the top, the leds at the bottom,
// and the falling led.
// The falling led can be -1 for no falling led.
// The led with index 0 is at the top.
// The led with index 15 is at the bottom.
// The sand barrier is between led 7 and 8.
void showSand(int top, int bottom, int fall, const CRGB color)
{
// Start with all leds off
FastLED.clear();
// The leds at the top
for (int i = 8-top; i < 8; i++)
leds[i] = color;
// The leds at the bottom
for (int i = NUM_LEDS-bottom; i<NUM_LEDS; i++)
leds[i] = color;
// The falling led
if(fall >= 0)
leds[fall] = color;
FastLED.show();
}
Hourglass