//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 9 // 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; int drop_time = 300; //in ms - will be changed to 1000 after manual setting minutes/hours with rotary-encoder LedControl lc = LedControl(PIN_DATAIN, PIN_CLK, PIN_LOAD, 2); NonBlockDelay d; int resetCounter = 0; bool alarmWentOff = true; //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 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 = slice0; m--) { lc.setXY(MATRIX_B, 7, m, true); lc.setXY(MATRIX_B, m, 7, true); } for (byte m=0; m<8; m++) { lc.setXY(MATRIX_A, 0, m, true); lc.setXY(MATRIX_A, m, 0, true); } for (byte m=7; m>0; m--) { lc.setXY(MATRIX_A, 7, m, true); lc.setXY(MATRIX_A, m, 7, true); } } 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() { drop_time = 1000; 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(); } } void setup() { Serial.begin(9600); // while (!Serial) { // ; // wait for serial port to connect. Needed for native USB // } Serial.println("Running ..... "); pinMode(PIN_BUZZER, OUTPUT); // 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); //Serial.println(digitalPinToInterrupt(PIN_ENC_1)); //Serial.println(digitalPinToInterrupt(PIN_ENC_2)); digitalWrite(PIN_BUZZER, HIGH); 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); //Check if rotary-encoder button was pushed if(digitalRead(PIN_ENC_BUTTON)==0) if((long)(millis() - lastButtonPushMillis) >= DEBOUNCE_THRESHOLD) buttonPush() ; // 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; } bool moved = updateMatrix(); bool dropped = dropParticle(); // alarm when everything is in the bottom part if (!moved && !dropped && !alarmWentOff && (countParticles(getTopMatrix()) == 0)) { alarmWentOff = true; Serial.println("Alarm reached ..... "); alarm(); } // reset alarm flag next time a particle was dropped if (dropped) { alarmWentOff = false; } }