#include "Arduino.h"
#include "LedControl.h"
#include "Delay.h"
// NOVAS INCLUSÕES PARA O MPU6050 (I2C)
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#define MATRIX_A 1
#define MATRIX_B 0
// NOVOS VALORES DE LIMIAR BASEADOS EM 'g' (aceleração da gravidade)
#define ACC_G 9.80665 // Aceleração da gravidade em m/s^2
#define G_THRESHOLD_GRAVITY (0.7 * ACC_G)
#define G_THRESHOLD_FLAT (0.3 * ACC_G)
// Matrix
#define PIN_DATAIN 5
#define PIN_CLK 7
#define PIN_LOAD 6
#define PIN_BUZZER 11
// This takes into account how the matrixes are mounted
#define ROTATION_OFFSET 90
#define DELAY_FRAME 100
#define DEBUG_OUTPUT 1
#define MODE_HOURGLASS 0
byte delayHours = 0;
byte delayMinutes = 1;
int mode = MODE_HOURGLASS;
// Objeto MPU6050 (GLOBAL)
Adafruit_MPU6050 mpu;
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() {
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;
return !lc.getXY(addr, getLeft(x, y));
}
bool canGoRight(int addr, int x, int y) {
if (y == 7) return false;
return !lc.getXY(addr, getRight(x, y));
}
bool canGoDown(int addr, int x, int y) {
if (y == 7) return false;
if (x == 0) return false;
if (!canGoLeft(addr, x, y)) return false;
if (!canGoRight(addr, x, y)) return false;
return !lc.getXY(addr, getDown(x, y));
}
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;
}
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) {
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));
}
}
}
/**
* LÊ A GRAVIDADE USANDO O MPU6050
* Retorna o ângulo de rotação (0, 90, 180, 270)
*/
int getGravity() {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
// A aceleração é lida em m/s^2.
float accX = a.acceleration.x;
float accY = a.acceleration.y;
// 0 graus (Vertical, topo em MATRIX_A) - Assumindo que +Y está apontando para cima
if (accY > G_THRESHOLD_GRAVITY && abs(accX) < G_THRESHOLD_FLAT) {
return 0;
}
// 180 graus (Upside-down/Invertido, topo em MATRIX_B) - Assumindo que -Y está apontando para cima
if (accY < -G_THRESHOLD_GRAVITY && abs(accX) < G_THRESHOLD_FLAT) {
return 180;
}
// 90 graus (De lado, topo em MATRIX_A) - Assumindo que +X está apontando para cima
if (accX > G_THRESHOLD_GRAVITY && abs(accY) < G_THRESHOLD_FLAT) {
return 90;
}
// 270 graus (De lado, topo em MATRIX_B) - Assumindo que -X está apontando para cima
if (accX < -G_THRESHOLD_GRAVITY && abs(accY) < G_THRESHOLD_FLAT) {
return 270;
}
// Se estiver em transição, mantém a última orientação
return gravity;
}
int getTopMatrix() {
// A lógica original era: 90 ou 0 = A topo, B fundo.
// E: 180 ou 270 = B topo, A fundo.
return (getGravity() == 90 || getGravity() == 0) ? MATRIX_A : MATRIX_B;
}
int getBottomMatrix() {
// O oposto de getTopMatrix
return (getGravity() != 90 && getGravity() != 0) ? MATRIX_A : MATRIX_B;
}
void resetTime() {
for (byte i=0; i<2; i++) {
lc.clearDisplay(i);
}
fill(getTopMatrix(), 60);
d.Delay(getDelayDrop() * 1000);
}
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);
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;
if (moveParticle(MATRIX_B, x, y)) {
somethingMoved = true;
};
if (moveParticle(MATRIX_A, x, y)) {
somethingMoved = true;
}
}
}
return somethingMoved;
}
/**
* Funcao dropParticle CORRIGIDA para usar aberturas condicionais com base na orientação.
*/
boolean dropParticle() {
if (d.Timeout()) {
d.Delay(getDelayDrop() * 1000);
int top = getTopMatrix();
int bottom = getBottomMatrix();
int currentGravity = getGravity();
int exitX, exitY, entryX, entryY;
// ******************************************************
// LÓGICA DE ABERTURA CONDICIONAL (Coordenadas FÍSICAS/Transformadas):
// ******************************************************
if (currentGravity == 0 || currentGravity == 180) {
// ORIENTAÇÃO VERTICAL (0 e 180 graus)
// SAÍDA: Canto inferior esquerdo físico (0, 7)
// ENTRADA: Canto superior direito físico (7, 0)
exitX = 0;
exitY = 7;
entryX = 7;
entryY = 0;
} else {
// ORIENTAÇÃO HORIZONTAL (90 e 270 graus)
// ESTES VALORES DEVEM SER AJUSTADOS COM BASE NA SUA MONTAGEM FÍSICA!
// Por exemplo, se o bico agora for o canto inferior direito (7, 7) e entrada o (0, 0)
exitX = 7;
exitY = 7;
entryX = 0;
entryY = 0;
// Se a abertura for o outro canto:
// exitX = 0; entryX = 7;
// exitY = 0; entryY = 7;
}
// Verificação de estado usando coordenadas FÍSICAS (getXY)
bool topHasParticle = lc.getXY(top, exitX, exitY);
bool bottomIsFree = !lc.getXY(bottom, entryX, entryY);
if (topHasParticle && bottomIsFree) {
// Ação: Remove do topo, Adiciona no fundo usando coordenadas FÍSICAS (setXY)
lc.setXY(top, exitX, exitY, false);
lc.setXY(bottom, entryX, entryY, true);
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);
}
}
/**
* Setup
*/
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0));
for (byte i=0; i<2; i++) {
lc.shutdown(i,false);
lc.setIntensity(i,1);
}
// --- INICIALIZAÇÃO DO MPU6050 ---
Wire.begin(); // Inicia comunicação I2C
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip, check wiring!");
while (1) {
delay(10);
}
}
Serial.println("MPU6050 Found!");
// Configurações do MPU6050 (CORRIGIDO MPU6050_BAND_21_HZ)
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
// ----------------------------------------------
resetTime();
}
/**
* Main loop
*/
void loop() {
delay(DELAY_FRAME);
gravity = getGravity();
lc.setRotation((ROTATION_OFFSET + gravity) % 360);
bool moved = updateMatrix();
bool dropped = dropParticle();
if (!moved && !dropped && !alarmWentOff && (countParticles(getTopMatrix()) == 0)) {
alarmWentOff = true;
alarm();
}
if (dropped) {
alarmWentOff = false;
}
}