Skip to content

Instantly share code, notes, and snippets.

@EDISON-SCIENCE-CORNER
Created June 12, 2021 17:03
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save EDISON-SCIENCE-CORNER/9a33d6693f959a75e11d310e047d1148 to your computer and use it in GitHub Desktop.
Save EDISON-SCIENCE-CORNER/9a33d6693f959a75e11d310e047d1148 to your computer and use it in GitHub Desktop.
//code credits Fabrizio Branca
//EDISON SCIENCE CORNER
#include "Arduino.h"
#include "LedControl.h"
#include "Delay.h"
#define MATRIX_A 1
#define MATRIX_B 0
// Values are 260/330/400
#define ACC_THRESHOLD_LOW 300
#define ACC_THRESHOLD_HIGH 360
// Matrix
#define PIN_DATAIN 5
#define PIN_CLK 4
#define PIN_LOAD 6
// Accelerometer
#define PIN_X A1
#define PIN_Y A2
// Rotary Encoder
#define PIN_ENC_1 3
#define PIN_ENC_2 2
#define PIN_ENC_BUTTON 7
#define PIN_BUZZER 14
// This takes into account how the matrixes are mounted
#define ROTATION_OFFSET 90
// in milliseconds
#define DEBOUNCE_THRESHOLD 500
#define DELAY_FRAME 100
#define DEBUG_OUTPUT 1
#define MODE_HOURGLASS 0
#define MODE_SETMINUTES 1
#define MODE_SETHOURS 2
byte delayHours = 0;
byte delayMinutes = 1;
int mode = MODE_HOURGLASS;
int gravity;
LedControl lc = LedControl(PIN_DATAIN, PIN_CLK, PIN_LOAD, 2);
NonBlockDelay d;
int resetCounter = 0;
bool alarmWentOff = false;
/**
* Get delay between particle drops (in seconds)
*/
long getDelayDrop() {
// since we have exactly 60 particles we don't have to multiply by 60 and then divide by the number of particles again :)
return delayMinutes + delayHours * 60;
}
#if DEBUG_OUTPUT
void printmatrix() {
Serial.println(" 0123-4567 ");
for (int y = 0; y<8; y++) {
if (y == 4) {
Serial.println("|----|----|");
}
Serial.print(y);
for (int x = 0; x<8; x++) {
if (x == 4) {
Serial.print("|");
}
Serial.print(lc.getXY(0,x,y) ? "X" :" ");
}
Serial.println("|");
}
Serial.println("-----------");
}
#endif
coord getDown(int x, int y) {
coord xy;
xy.x = x-1;
xy.y = y+1;
return xy;
}
coord getLeft(int x, int y) {
coord xy;
xy.x = x-1;
xy.y = y;
return xy;
}
coord getRight(int x, int y) {
coord xy;
xy.x = x;
xy.y = y+1;
return xy;
}
bool canGoLeft(int addr, int x, int y) {
if (x == 0) return false; // not available
return !lc.getXY(addr, getLeft(x, y)); // you can go there if this is empty
}
bool canGoRight(int addr, int x, int y) {
if (y == 7) return false; // not available
return !lc.getXY(addr, getRight(x, y)); // you can go there if this is empty
}
bool canGoDown(int addr, int x, int y) {
if (y == 7) return false; // not available
if (x == 0) return false; // not available
if (!canGoLeft(addr, x, y)) return false;
if (!canGoRight(addr, x, y)) return false;
return !lc.getXY(addr, getDown(x, y)); // you can go there if this is empty
}
void goDown(int addr, int x, int y) {
lc.setXY(addr, x, y, false);
lc.setXY(addr, getDown(x,y), true);
}
void goLeft(int addr, int x, int y) {
lc.setXY(addr, x, y, false);
lc.setXY(addr, getLeft(x,y), true);
}
void goRight(int addr, int x, int y) {
lc.setXY(addr, x, y, false);
lc.setXY(addr, getRight(x,y), true);
}
int countParticles(int addr) {
int c = 0;
for (byte y=0; y<8; y++) {
for (byte x=0; x<8; x++) {
if (lc.getXY(addr, x, y)) {
c++;
}
}
}
return c;
}
bool moveParticle(int addr, int x, int y) {
if (!lc.getXY(addr,x,y)) {
return false;
}
bool can_GoLeft = canGoLeft(addr, x, y);
bool can_GoRight = canGoRight(addr, x, y);
if (!can_GoLeft && !can_GoRight) {
return false; // we're stuck
}
bool can_GoDown = canGoDown(addr, x, y);
if (can_GoDown) {
goDown(addr, x, y);
} else if (can_GoLeft&& !can_GoRight) {
goLeft(addr, x, y);
} else if (can_GoRight && !can_GoLeft) {
goRight(addr, x, y);
} else if (random(2) == 1) { // we can go left and right, but not down
goLeft(addr, x, y);
} else {
goRight(addr, x, y);
}
return true;
}
void fill(int addr, int maxcount) {
int n = 8;
byte x,y;
int count = 0;
for (byte slice = 0; slice < 2*n-1; ++slice) {
byte z = slice<n ? 0 : slice-n + 1;
for (byte j = z; j <= slice-z; ++j) {
y = 7-j;
x = (slice-j);
lc.setXY(addr, x, y, (++count <= maxcount));
}
}
}
/**
* Detect orientation using the accelerometer
*
* | up | right | left | down |
* --------------------------------
* 400 | | | y | x |
* 330 | y | x | x | y |
* 260 | x | y | | |
*/
int getGravity() {
int x = analogRead(PIN_X);
int y = analogRead(PIN_Y);
if (y < ACC_THRESHOLD_LOW) { return 0; }
if (x > ACC_THRESHOLD_HIGH) { return 90; }
if (y > ACC_THRESHOLD_HIGH) { return 180; }
if (x < ACC_THRESHOLD_LOW) { return 270; }
}
int getTopMatrix() {
return (getGravity() == 90) ? MATRIX_A : MATRIX_B;
}
int getBottomMatrix() {
return (getGravity() != 90) ? MATRIX_A : MATRIX_B;
}
void resetTime() {
for (byte i=0; i<2; i++) {
lc.clearDisplay(i);
}
fill(getTopMatrix(), 60);
d.Delay(getDelayDrop() * 1000);
}
/**
* Traverse matrix and check if particles need to be moved
*/
bool updateMatrix() {
int n = 8;
bool somethingMoved = false;
byte x,y;
bool direction;
for (byte slice = 0; slice < 2*n-1; ++slice) {
direction = (random(2) == 1); // randomize if we scan from left to right or from right to left, so the grain doesn't always fall the same direction
byte z = slice<n ? 0 : slice-n + 1;
for (byte j = z; j <= slice-z; ++j) {
y = direction ? (7-j) : (7-(slice-j));
x = direction ? (slice-j) : j;
// for (byte d=0; d<2; d++) { lc.invertXY(0, x, y); delay(50); }
if (moveParticle(MATRIX_B, x, y)) {
somethingMoved = true;
};
if (moveParticle(MATRIX_A, x, y)) {
somethingMoved = true;
}
}
}
return somethingMoved;
}
/**
* Let a particle go from one matrix to the other
*/
boolean dropParticle() {
if (d.Timeout()) {
d.Delay(getDelayDrop() * 1000);
if (gravity == 0 || gravity == 180) {
if ((lc.getRawXY(MATRIX_A, 0, 0) && !lc.getRawXY(MATRIX_B, 7, 7)) ||
(!lc.getRawXY(MATRIX_A, 0, 0) && lc.getRawXY(MATRIX_B, 7, 7))
) {
// for (byte d=0; d<8; d++) { lc.invertXY(0, 0, 7); delay(50); }
lc.invertRawXY(MATRIX_A, 0, 0);
lc.invertRawXY(MATRIX_B, 7, 7);
tone(PIN_BUZZER, 440, 10);
return true;
}
}
}
return false;
}
void alarm() {
for (int i=0; i<5; i++) {
tone(PIN_BUZZER, 440, 200);
delay(1000);
}
}
void resetCheck() {
int z = analogRead(A3);
if (z > ACC_THRESHOLD_HIGH || z < ACC_THRESHOLD_LOW) {
resetCounter++;
Serial.println(resetCounter);
} else {
resetCounter = 0;
}
if (resetCounter > 20) {
Serial.println("RESET!");
resetTime();
resetCounter = 0;
}
}
void displayLetter(char letter, int matrix) {
// Serial.print("Letter: ");
// Serial.println(letter);
lc.clearDisplay(matrix);
lc.setXY(matrix, 1,4, true);
lc.setXY(matrix, 2,3, true);
lc.setXY(matrix, 3,2, true);
lc.setXY(matrix, 4,1, true);
lc.setXY(matrix, 3,6, true);
lc.setXY(matrix, 4,5, true);
lc.setXY(matrix, 5,4, true);
lc.setXY(matrix, 6,3, true);
if (letter == 'M') {
lc.setXY(matrix, 4,2, true);
lc.setXY(matrix, 4,3, true);
lc.setXY(matrix, 5,3, true);
}
if (letter == 'H') {
lc.setXY(matrix, 3,3, true);
lc.setXY(matrix, 4,4, true);
}
}
void renderSetMinutes() {
fill(getTopMatrix(), delayMinutes);
displayLetter('M', getBottomMatrix());
}
void renderSetHours() {
fill(getTopMatrix(), delayHours);
displayLetter('H', getBottomMatrix());
}
void knobClockwise() {
Serial.println("Clockwise");
if (mode == MODE_SETHOURS) {
delayHours = constrain(delayHours+1, 0, 64);
renderSetHours();
} else if(mode == MODE_SETMINUTES) {
delayMinutes = constrain(delayMinutes+1, 0, 64);
renderSetMinutes();
}
Serial.print("Delay: ");
Serial.println(getDelayDrop());
}
void knobCounterClockwise() {
Serial.println("Counterclockwise");
if (mode == MODE_SETHOURS) {
delayHours = constrain(delayHours-1, 0, 64);
renderSetHours();
} else if (mode == MODE_SETMINUTES) {
delayMinutes = constrain(delayMinutes-1, 0, 64);
renderSetMinutes();
}
Serial.print("Delay: ");
Serial.println(getDelayDrop());
}
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
long lastencoderValue = 0;
long lastValue = 0;
void updateEncoder() {
int MSB = digitalRead(PIN_ENC_1); //MSB = most significant bit
int LSB = digitalRead(PIN_ENC_2); //LSB = least significant bit
int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
int sum = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue--;
if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue++;
// Serial.print("Value: ");
// Serial.println(encoderValue);
if ((encoderValue % 4) == 0) {
int value = encoderValue / 4;
if (value > lastValue) knobClockwise();
if (value < lastValue) knobCounterClockwise();
lastValue = value;
}
lastEncoded = encoded; //store this value for next time
}
/**
* Button callback (incl. software debouncer)
* This switches between the modes (normal, set minutes, set hours)
*/
volatile unsigned long lastButtonPushMillis;
void buttonPush() {
if((long)(millis() - lastButtonPushMillis) >= DEBOUNCE_THRESHOLD) {
mode = (mode+1) % 3;
Serial.print("Switched mode to: ");
Serial.println(mode);
lastButtonPushMillis = millis();
if (mode == MODE_SETMINUTES) {
lc.backup(); // we only need to back when switching from MODE_HOURGLASS->MODE_SETMINUTES
renderSetMinutes();
}
if (mode == MODE_SETHOURS) {
renderSetHours();
}
if (mode == MODE_HOURGLASS) {
lc.clearDisplay(0);
lc.clearDisplay(1);
lc.restore();
resetTime();
}
}
}
/**
* Setup
*/
void setup() {
Serial.begin(9600);
// while (!Serial) {
// ; // wait for serial port to connect. Needed for native USB
// }
// setup rotary encoder
pinMode(PIN_ENC_1, INPUT);
pinMode(PIN_ENC_2, INPUT);
pinMode(PIN_ENC_BUTTON, INPUT);
digitalWrite(PIN_ENC_1, HIGH); //turn pullup resistor on
digitalWrite(PIN_ENC_2, HIGH); //turn pullup resistor on
digitalWrite(PIN_ENC_BUTTON, HIGH); //turn pullup resistor on
attachInterrupt(digitalPinToInterrupt(PIN_ENC_1), updateEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(PIN_ENC_2), updateEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(PIN_ENC_BUTTON), buttonPush, RISING);
// Serial.println(digitalPinToInterrupt(PIN_ENC_1));
// Serial.println(digitalPinToInterrupt(PIN_ENC_2));
// Serial.println(digitalPinToInterrupt(PIN_ENC_BUTTON));
randomSeed(analogRead(A0));
// init displays
for (byte i=0; i<2; i++) {
lc.shutdown(i,false);
lc.setIntensity(i,0);
}
resetTime();
}
/**
* Main loop
*/
void loop() {
delay(DELAY_FRAME);
// update the driver's rotation setting. For the rest of the code we pretend "down" is still 0,0 and "up" is 7,7
gravity = getGravity();
lc.setRotation((ROTATION_OFFSET + gravity) % 360);
// handle special modes
if (mode == MODE_SETMINUTES) {
renderSetMinutes(); return;
} else if (mode == MODE_SETHOURS) {
renderSetHours(); return;
}
// resetCheck(); // reset now happens when pushing a button
bool moved = updateMatrix();
bool dropped = dropParticle();
// alarm when everything is in the bottom part
if (!moved && !dropped && !alarmWentOff && (countParticles(getTopMatrix()) == 0)) {
alarmWentOff = true;
alarm();
}
// reset alarm flag next time a particle was dropped
if (dropped) {
alarmWentOff = false;
}
}
@Arvin-666
Copy link

Where can I get the file to "Delay.h"?

@trgraglia
Copy link

The libraries can be found in the google drive links under the youtube video of the build: https://www.youtube.com/watch?v=AyzsMfBe5JA

@sumondigi
Copy link

Compilation error: 'coord' does not name a type; did you mean 'word'?
What shoul ido???
i couln't figure out the prob..

@JuandiegoRiveraA
Copy link

C:\Users\USER\Desktop\ahg\ahg\hourglass\hourglass.ino:6:10: fatal error: LedControL.h: No such file or directory
#include "LedControL.h"
^~~~~~~~~~~~~~
compilation terminated.

exit status 1

Compilation error: LedControL.h: No such file or directory

I get this error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment