Arduino

Elektronik Kum Saati Yapımı

Herkese selamlar arkadaşlar, bu yazıda dot matrix kullanarak elektronik kum saati yapımının detaylarına göz atacağız.

Bu projenin esin kaynağı olan ve aynı zamanda kod kısmında referans aldığımız proje Fabrizio Branca’ya ait olan kum saati projesi. Proje videosuna aşağıdaki link üzerinden ulaşabilirsiniz.
https://www.youtube.com/watch?v=U-vmEOuy348

Malzemeler

Projenin ana malzemesi kum saati görüntüsünü oluşturacak dot matrixler. Dot matrix modülleri üzerindeki LEDleri kullanarak kum saati görüntüsünü sağlamış olacağız. Bir diğer önemli parça ise ADXL345 İvme ölçer.

ADXL345 Sensörü Özellikleri:

  • Giriş Voltajı: 3-5V
  • Düşük güç tüketimi(40uA)
  • Serbest düşüş algılama
  • SPI ve I²C arayüzü

Sensörün üç eksende verebildiği açısal ivme değerleri ile saatin eksen hareketlerini tespit ederek LED matrixleri kontrol etmiş olacağız.

Projenin ana malzemeleri:

Bağlantı Şeması

Sensör ve dot matrixlerin Arduino ile olan bağlantılarını aşağıdaki şema üzerinde görebilirsiniz.

Tüm bağlantıları yaparak projeyi kod aşamasına hazır hale getiriyoruz.

3D Dosyalar

Projeyi kum saati görünümüne çeviren tasarımın tüm parçalarına buraya tıklayarak ulaşabilirsiniz.

Proje Kodu

Projenin kod kısmında proje fikrinin de esin kaynağı olan Fabrizio Branca’nın kodundan faydalanarak kendi sensörümüze göre uyarladık. Kod kısmında işe kendi kodunda kullandığı kütüphaneleri indirerek başlıyoruz. Ardından kütüphaneleri Arduino IDE’ye dahil ediyoruz.

Kütüphaneler: https://github.com/fbrnc/Arduino_Hourglass/tree/master/lib
(Delay ve LedControl kütüphanelerini indiriyoruz)

Projenin ana parçalarından olan ADXL345 sensörünün kullanım öncesi kalibre edilmesi projemizin daha sağlıklı çalışmasını sağlayacaktır. ADXL sensörünü otomatik olarak kalibre etmek için Adafruit’in ADXL345 kütüphanesini (Eğer yüklü değilse Adafruit’in unified sensor kütüphanesini de kurmanız gerekiyor.) kurabilirsiniz. Ardından aşağıdaki linkteki aşamaları takip ederek sensörü otomatik bir şekilde kalibre edebilirsiniz.
https://learn.adafruit.com/adxl345-digital-accelerometer/programming

ADXL345 sensörü ile ilgili detaylı bilgi için bu videoya da göz atabilirsiniz: https://www.youtube.com/watch?v=KMhbV1p3MWk

Ön hazırlıklar tamamlandıysa kod yüklenmeye hazır. Kod içerisindeki THRESHOLD değerleri ile oynayarak kum saatinin sensöre göre dönüş hassasiyetini ayarlayabilirsiniz.

Koddaki delayHours ve delayMinutes değişkenlerinin de kum saatinin boşalma süresini temsil ettiğini eklemek isterim.

#include "Arduino.h"
#include "LedControl.h"
#include "Delay.h"
#include <Wire.h>

int ADXL345 = 0x53; // The ADXL345 sensor I2C address
float x, y;

#define  MATRIX_A  1
#define MATRIX_B  0

#define ACC_THRESHOLD_LOW -0.5
#define ACC_THRESHOLD_HIGH 0

// Matrix
#define PIN_DATAIN 5
#define PIN_CLK 4
#define PIN_LOAD 6

// This takes into account how the matrixes are mounted
#define ROTATION_OFFSET 90

// in milliseconds
#define DEBOUNCE_THRESHOLD 500
#define DELAY_FRAME 80


byte delayHours = 0;
byte delayMinutes = 1;

int gravity;
LedControl lc = LedControl(PIN_DATAIN, PIN_CLK, PIN_LOAD, 2);
NonBlockDelay d;
int resetCounter = 0;

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;
}

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));
    }
  }
}

int getGravity() {
  Wire.beginTransmission(ADXL345);
  Wire.write(0x32); // Start with register 0x32 (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(ADXL345, 6, true); // Read 6 registers total, each axis value is stored in 2 registers
  x = ( Wire.read()| Wire.read() << 8); // X-axis value
  x = x/256; //For a range of +-2g, we need to divide the raw values by 256, according to the datasheet
  y = ( Wire.read()| Wire.read() << 8); // Y-axis value
  y = y/256;
  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);
        return true;
      }
    }
  }
  return false;
}


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;
  }
}

/**
 * Setup
 */
void setup() {
  Serial.begin(9600);

  Wire.begin(); // Initiate the Wire library
  // Set ADXL345 in measuring mode
  Wire.beginTransmission(ADXL345); // Start communicating with the device 
  Wire.write(0x2D); // Access/ talk to POWER_CTL Register - 0x2D
  // Enable measurement
  Wire.write(8); // (8dec -> 0000 1000 binary) Bit D3 High for measuring enable 
  Wire.endTransmission();
  delay(10);

  // init displays
  for (byte i=0; i<2; i++) {
    lc.shutdown(i,false);
    lc.setIntensity(i,2);
  }

  resetTime();
}

/**
 * Main loop
 */
void loop() {
  delay(DELAY_FRAME);

  gravity = getGravity();
  lc.setRotation((ROTATION_OFFSET + gravity) % 360);

  bool moved = updateMatrix();
  bool dropped = dropParticle();
  }

Umarım keyifli bir proje olmuştur. Gelecek videolar için proje fikirlerinizi yorum kısmında paylaşmayı unutmayın. Bir başka projede görüşünceye dek, hoşça kalın 🙂

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *