DIY ELECTRONICSEN

← Tüm projeler

Dijital Su Terazisi

Orta45 dk

Breadboard'un kendisini bir su terazisine çeviren proje: MPU6050'nin ivmeölçeri yerçekiminin yönünü ölçer, Arduino iki eksendeki eğimi derece olarak hesaplar. OLED ekranın solunda gerçek bir su terazisindeki gibi bir kabarcık yüksek tarafa kaçar, sağında X ve Y eğimi yazar; ikisi de yarım derecenin altına inince ekranda "DÜZ" yazar ve buzzer iki kısa bip çalar. Bir sensörü kütüphanesiz, kayıtlarını doğrudan okuyarak kullanmayı, yerçekiminden açı hesaplamayı (atan2), titreyen ölçümü yumuşatmayı ve iki I2C modülünü aynı hatta bağlamayı öğretir. Kitaplık Yöneticisi'nden "Adafruit SSD1306" ve "Adafruit GFX" kurulur.

Uygulamada adım adım yap

Gerekli malzemeler

Bağlantı adımları

  1. 1

    Güç sıralarını bağla ve birleştir

    Güç sıralarını bağla ve birleştir

    Kırmızı erkek-erkek jumper kabloyu Arduino'nun güç başlığındaki 5V pinine tak, diğer ucunu breadboard'un alttaki kırmızı çizgili (+) güç sırasına bağla. Siyah kabloyu aynı başlıktaki GND pinine tak, alttaki mavi çizgili (−) sıraya bağla. Breadboard'un üstteki ve alttaki güç sıraları birbirine bağlı değildir: breadboard'un sağ ucunda kırmızı bir kabloyla alttaki (+) sırayı üstteki (+) sıraya, siyah bir kabloyla alttaki (−) sırayı üstteki (−) sıraya bağla. Artık dört sıranın hepsinde 5V ve GND var.

    • Arduino GND → alttaki mavi çizgili (−) sıra (siyah kablo)
    • Arduino 5V → alttaki kırmızı çizgili (+) sıra (kırmızı kablo)
    • alttaki kırmızı çizgili (+) sıra → üstteki kırmızı çizgili (+) sıra (kırmızı kablo)
    • alttaki mavi çizgili (−) sıra → üstteki mavi çizgili (−) sıra (siyah kablo)
  2. 2

    MPU6050'yi tak ve bağla

    MPU6050'yi tak ve bağla

    MPU6050 (GY-521) modülünü, sekiz pini breadboard'un alt yarısında E sırasına gelecek şekilde tak: VCC 3., GND 4., SCL 5., SDA 6. sütuna gelsin; XDA, XCL, AD0 ve INT 7–10. sütunlara oturur ama kullanılmaz. Modülün gövdesi ortadaki boşluğun üstüne uzanır. Kısa kırmızı kabloyla 3. sütunu alttaki (+) sıraya, kısa siyah kabloyla 4. sütunu alttaki (−) sıraya bağla. Sarı kabloyla Arduino'nun A5 pinini 5. sütuna, mavi kabloyla A4 pinini 6. sütuna bağla. Modülü breadboard'a düz yatacak şekilde iyice bastır: terazi breadboard'un kendisidir, modül eğik durursa ölçüm de eğik olur.

    • MPU6050 VCC → 3. sütun, alt yarı
    • MPU6050 GND → 4. sütun, alt yarı
    • MPU6050 SCL → 5. sütun, alt yarı
    • MPU6050 SDA → 6. sütun, alt yarı
    • MPU6050 XDA → 7. sütun, alt yarı
    • MPU6050 XCL → 8. sütun, alt yarı
    • MPU6050 AD0 → 9. sütun, alt yarı
    • MPU6050 INT → 10. sütun, alt yarı
    • 3. sütun, alt yarı → alttaki kırmızı çizgili (+) sıra (kırmızı kablo)
    • 4. sütun, alt yarı → alttaki mavi çizgili (−) sıra (siyah kablo)
    • Arduino A5 → 5. sütun, alt yarı (sarı kablo)
    • Arduino A4 → 6. sütun, alt yarı (mavi kablo)
  3. 3

    OLED ekranı tak ve bağla

    OLED ekranı tak ve bağla

    OLED ekranı, dört pini üst yarıda F sırasına gelecek şekilde tak: GND 27., VCC 28., SCL 29., SDA 30. sütuna gelsin; ekran ortadaki boşluğun üstüne uzanır. Bazı OLED'lerde VCC ile GND'nin yeri terstir: kabloları pinlerin üstünde yazan adlara göre bağla. Kısa siyah kabloyla GND'nin sütununu üstteki (−) sıraya, kısa kırmızı kabloyla VCC'nin sütununu üstteki (+) sıraya bağla. Sarı kabloyla Arduino'nun üst sırasında AREF'in yanındaki SCL pinini 29. sütuna, mavi kabloyla onun yanındaki SDA pinini 30. sütuna bağla. Uno R3'te bu iki pin A4 ve A5 ile aynı hattır: MPU6050 ile OLED aynı I2C hattını paylaşır, kod onları adresleriyle ayırır (MPU6050 0x68, OLED 0x3C).

    • OLED ekran GND → 27. sütun, üst yarı
    • OLED ekran VCC → 28. sütun, üst yarı
    • OLED ekran SCL → 29. sütun, üst yarı
    • OLED ekran SDA → 30. sütun, üst yarı
    • 27. sütun, üst yarı → üstteki mavi çizgili (−) sıra (siyah kablo)
    • 28. sütun, üst yarı → üstteki kırmızı çizgili (+) sıra (kırmızı kablo)
    • Arduino SCL (AREF'in yanındaki) → 29. sütun, üst yarı (sarı kablo)
    • Arduino SDA (AREF'in yanındaki) → 30. sütun, üst yarı (mavi kablo)
  4. 4

    Buzzer'ı tak ve bağla

    Buzzer'ı tak ve bağla

    Pasif buzzer'ın + bacağını üst yarıda 15. sütuna, öbür bacağını 16. sütuna tak; + bacak genelde daha uzundur ve gövdesinde + işareti vardır. Turuncu kabloyla Arduino'nun 8 numaralı pinini 15. sütuna bağla (BUZZER_PIN 8). Kısa siyah bir kabloyla 16. sütunu üstteki (−) sıraya bağla.

    • buzzer + bacak → 15. sütun, üst yarı
    • buzzer − bacak → 16. sütun, üst yarı
    • Arduino pin 8 → 15. sütun, üst yarı (turuncu kablo)
    • 16. sütun, üst yarı → üstteki mavi çizgili (−) sıra (siyah kablo)

Arduino kodu

// Kitaplık Yöneticisi'nden kurulacaklar:
// "Adafruit SSD1306" ve "Adafruit GFX"
// (MPU6050 kitaplıksız, Wire ile okunur)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

const int BUZZER_PIN = 8;
// MPU6050'nin adresi (AD0 boşta: 0x68)
const byte MPU_ADDR = 0x68;

// Bildiğin düz bir yerde X ya da Y sıfır
// değilse, gösterdiği açıyı buraya yaz
const float OFFSET_X = 0.0;
const float OFFSET_Y = 0.0;
// Kabarcık ters giderse: FLIP bir ekseni
// çevirir, SWAP X ile Y'yi değiştirir
const bool FLIP_X = false;
const bool FLIP_Y = false;
const bool SWAP_AXES = false;

// Bu kadar yakınsa (derece) düz sayılır;
// OUT kadar açılınca düzlük biter
const float LEVEL_IN = 0.5;
const float LEVEL_OUT = 1.0;

// Terazinin ölçüleri (piksel)
const int CX = 31;
const int CY = 31;
const int RING_R = 30;
const int TARGET_R = 7;
const int BUBBLE_R = 5;
const int TRAVEL = 23;
// Bir derecelik eğim kaç piksel kaydırır
const float SCALE = 2.0;

// 128x64 ekran; I2C adresi çoğunda 0x3C
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

// Yumuşatılmış eğimler (derece)
float tiltX = 0;
float tiltY = 0;
bool level = false;
bool mpuOk = false;
bool firstReading = true;
int beepsLeft = 0;
unsigned long beepAt = 0;
unsigned long lastRead = 0;
unsigned long lastDraw = 0;
unsigned long lastPrint = 0;

void writeRegister(byte reg, byte value) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
}

// Uyandırır; MPU6050 yanıt verirse true
bool startMpu() {
  Wire.beginTransmission(MPU_ADDR);
  // Güç kaydı 0: uyku modundan çık
  Wire.write(0x6B);
  Wire.write(0);
  if (Wire.endTransmission() != 0) return false;
  // Titreşime karşı süzgeç (~44 Hz)
  writeRegister(0x1A, 3);
  return true;
}

// İki baytı tek sayı yapar (önce yüksek)
int read16() {
  byte high = Wire.read();
  byte low = Wire.read();
  return (int16_t)((high << 8) | low);
}

// İvmeölçerin üç ekseni; hatada false
bool readAccel(float &ax, float &ay, float &az) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x3B);
  if (Wire.endTransmission(false) != 0) return false;
  if (Wire.requestFrom((int)MPU_ADDR, 6) != 6) return false;
  ax = read16();
  ay = read16();
  az = read16();
  return true;
}

void readTilt() {
  float ax, ay, az;
  if (!readAccel(ax, ay, az)) {
    mpuOk = false;
    firstReading = true;
    return;
  }
  // Yerçekiminin eksenlere düşen payından
  // açı: eksen yükseldikçe pay büyür
  float x = atan2(ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG;
  float y = atan2(ay, sqrt(ax * ax + az * az)) * RAD_TO_DEG;
  if (SWAP_AXES) {
    float t = x;
    x = y;
    y = t;
  }
  if (FLIP_X) x = -x;
  if (FLIP_Y) y = -y;
  x -= OFFSET_X;
  y -= OFFSET_Y;
  // İlk ölçüm olduğu gibi alınır (yoksa
  // 0'dan başlayıp düz görünürdü)
  if (firstReading) {
    tiltX = x;
    tiltY = y;
    firstReading = false;
  }
  // Yumuşatma: titreşen sayı göstermez
  tiltX += (x - tiltX) * 0.15;
  tiltY += (y - tiltY) * 0.15;
}

void checkLevel() {
  float worst = max(abs(tiltX), abs(tiltY));
  if (!level && worst <= LEVEL_IN) {
    level = true;
    // Düz olunca iki kısa bip
    beepsLeft = 2;
    beepAt = millis();
    Serial.println("Düz!");
  } else if (level && worst > LEVEL_OUT) {
    level = false;
  }
}

void playBeeps() {
  if (beepsLeft > 0 && millis() >= beepAt) {
    tone(BUZZER_PIN, 1800, 60);
    beepsLeft--;
    beepAt = millis() + 140;
  }
}

// Açıyı büyük yazar, yanına küçük derece
void printAngle(int x, int y, float angle) {
  oled.setTextSize(2);
  oled.setCursor(x, y);
  oled.print(angle, 1);
  int end = oled.getCursorX();
  if (end + 6 <= 128) {
    oled.setTextSize(1);
    oled.setCursor(end + 1, y);
    // 247 ekranın derece işareti
    oled.write(247);
  }
}

void drawScreen() {
  oled.clearDisplay();
  if (!mpuOk) {
    oled.setTextSize(1);
    oled.setCursor(0, 20);
    oled.println("MPU6050 yok");
    oled.println("Kablolara bak");
    oled.display();
    return;
  }
  // Sol: halka, hedef ve artı çizgileri
  oled.drawCircle(CX, CY, RING_R, SSD1306_WHITE);
  oled.drawCircle(CX, CY, TARGET_R, SSD1306_WHITE);
  oled.drawFastHLine(CX - RING_R, CY, RING_R - TARGET_R, SSD1306_WHITE);
  oled.drawFastHLine(CX + TARGET_R + 1, CY, RING_R - TARGET_R, SSD1306_WHITE);
  oled.drawFastVLine(CX, CY - RING_R, RING_R - TARGET_R, SSD1306_WHITE);
  oled.drawFastVLine(CX, CY + TARGET_R + 1, RING_R - TARGET_R, SSD1306_WHITE);

  // Kabarcık yüksek tarafa kaçar;
  // halkadan çıkmasın diye kısaltılır
  float dx = tiltX * SCALE;
  float dy = -tiltY * SCALE;
  float far = sqrt(dx * dx + dy * dy);
  if (far > TRAVEL) {
    dx = dx * TRAVEL / far;
    dy = dy * TRAVEL / far;
  }
  oled.fillCircle(CX + round(dx), CY + round(dy), BUBBLE_R, SSD1306_WHITE);

  // Sağ: iki eksenin açısı
  oled.setTextSize(1);
  oled.setCursor(66, 0);
  oled.print("X");
  printAngle(66, 9, tiltX);
  oled.setTextSize(1);
  oled.setCursor(66, 29);
  oled.print("Y");
  printAngle(66, 38, tiltY);
  if (level) {
    oled.setTextSize(1);
    oled.setCursor(66, 56);
    // \x9A ekranın Ü harfi
    oled.print("D\x9AZ");
  }
  oled.display();
}

void setup() {
  Serial.begin(9600);
  Wire.begin();
  // Hızlı I2C: ekran daha çabuk yenilenir
  Wire.setClock(400000);
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED bulunamadı");
    // Ekran yok: burada dur
    while (true) {}
  }
  oled.setTextColor(SSD1306_WHITE);
  // Türkçe Ü ve derece işareti için
  oled.cp437(true);
  mpuOk = startMpu();
  if (!mpuOk) Serial.println("MPU6050 bulunamadı: kablolara bak.");
}

void loop() {
  unsigned long now = millis();
  if (now - lastRead >= 20) {
    lastRead = now;
    // Kopan bağlantıyı yeniden dener
    if (!mpuOk) mpuOk = startMpu();
    if (mpuOk) readTilt();
    if (mpuOk) checkLevel();
  }
  playBeeps();
  if (now - lastDraw >= 100) {
    lastDraw = now;
    drawScreen();
  }
  if (mpuOk && now - lastPrint >= 500) {
    lastPrint = now;
    Serial.print("X: ");
    Serial.print(tiltX, 1);
    Serial.print("°  Y: ");
    Serial.print(tiltY, 1);
    Serial.println("°");
  }
}

Seri Monitörde göreceğin

SERIAL MONITOR · 9600 BAUD
X: 3.2°  Y: -1.4°
X: 1.1°  Y: -0.6°
Düz!
X: 0.3°  Y: -0.2°
X: 0.2°  Y: -0.1°

Sonuç

Kodu yükleyince OLED'in solunda bir halka ve içinde bir kabarcık, sağında X ve Y eğimi derece olarak görünür. Breadboard'u Arduino'yla birlikte bir masaya koy ve bir kenarını hafifçe kaldır: kabarcık kaldırdığın, yüksek tarafa kaçar ve o eksenin açısı artar. Kabarcığı ortadaki küçük halkaya getirdiğinde, yani iki eksen de yarım derecenin altına inince sağ altta "DÜZ" yazar ve buzzer iki kısa bip çalar; bir dereceden fazla eğince düzlük biter. Açılar Seri Monitöre de yarım saniyede bir yazılır. Kabarcık kaldırdığın tarafın tersine gidiyorsa koddaki FLIP_X ya da FLIP_Y'yi true yap; sağa sola eğince yukarı aşağı gidiyorsa SWAP_AXES'i true yap. Bildiğin düz bir yerde X ya da Y sıfır değilse, gösterdiği değeri OFFSET_X ya da OFFSET_Y'ye yaz: her sensörün küçük bir sapması olur. Ekranda "MPU6050 yok" yazıyorsa MPU6050'nin kablolarına bak; ekran hiç yanmıyorsa OLED'in kablolarını ve adresini (0x3C, bazılarında 0x3D) kontrol et.

Uygulamada her bağlantıyı tek tek işaretle, envanterindeki parçalarla eşleştir ve kaldığın yerden devam et.

Uygulamada adım adım yap