ESP32 I2C Kullanımı

ESP32 I2C Kullanımı esp32 i2c,esp32 i2c kullanımı,esp32 i2c lcd,esp32 i2c slave,esp32 i2c speed

ESP32 I2C (Inter-Integrated Circuit) çevre birimi; master (ana) veya slave (bağımlı) olarak çalışabilen iki adet bağımsız donanımsal I2C kontrolörüne sahiptir.

Modern gömülü sistem mimarilerinde ve Nesnelerin İnterneti (IoT) projelerinde, birden çok sensörün, ekranın ve harici çevre biriminin mikrodenetleyicilerle minimum pin kullanarak haberleşmesi büyük önem taşır.

ESP32’nin esnek GPIO matrisi sayesinde, varsayılan pinler haricinde neredeyse tüm dijital pinleri I2C veri yolu olarak yapılandırmak mümkündür. Bu kapsamlı rehberimizde, I2C iletişim protokolünün fiziksel elektriksel katmanını, pull-up direnci hesaplama formüllerini, ESP32’nin donanımsal I2C portlarını, I2C adres tarayıcı yazılımlarını, adres çakışmalarına karşı TCA9548A çoklayıcı ve çift bağımsız veri yolu (Wire / Wire1) kullanım senaryolarını en üst seviye mühendislik detaylarıyla inceleyeceğiz.

ESP32 I2C Kapak Görseli

ESP32 programlama adımlarını Arduino IDE üzerinde gerçekleştireceğiz. Gerekli araç zinciri ve kart sürücüsü kurulumları için Arduino IDE’ye ESP32 Desteği Kurmak rehberimizi inceleyebilirsiniz. Ayrıca I2C protokolünün temeli olan veri yolu kavramını daha iyi kavramak adına Veri Yolu (Bus) Nedir? yazımıza da göz atabilirsiniz.

Yazı İçeriği

I2C İletişim Protokolünün Fiziksel ve Elektriksel Karakteristiği

I2C, Philips Semiconductors (şimdiki NXP) tarafından geliştirilen, senkron, çift yönlü, yarım çift yönlü (half-duplex) ve çoklu master/slave desteğine sahip seri bir haberleşme protokolüdür. Protokol fiziksel katmanda sadece iki adet sinyal hattı kullanır:

  • SDA (Serial Data Line): Çift yönlü veri iletiminin yapıldığı hat.
  • SCL (Serial Clock Line): Master tarafından üretilen ve iletimi senkronize eden saat sinyali hattı.
ESP32 I2C Çoklu Cihaz Bağlantı Prensibi

Açık Kollektör/Drain ve Pull-up Direnci Hesabı

I2C hatlarını kullanan tüm cihazların çıkış katları açık-drain (open-drain) veya açık-kollektör yapısındadır. Hatlar kendi başlarına mantıksal yüksek (1) seviyesine çıkamaz, sadece hattı şaseye (GND) çekerek düşük (0) seviyesine getirebilirler. Bu nedenle, hatların varsayılan durumda mantıksal yüksekte kalabilmesi için V_{DD} hattına bağlı harici pull-up (yukarı çekme) dirençlerine ihtiyaç duyulur.

Optimum pull-up direnç değeri, hattın toplam parazitik sığası (C_b) ve çalışılan frekans limitleri doğrultusunda hesaplanır. Minimum ve maksimum direnç değerleri şu formüllerle belirlenir:

    \[R_{pull-up\_min} = \frac{V_{DD} - V_{OL}}{I_{OL}}\]

    \[R_{pull-up\_max} = \frac{t_r}{0.8473 \times C_b}\]

Burada V_{OL} maksimum alçak seviye voltaj eşiği, I_{OL} cihazların maksimum akım çekme kapasitesi, t_r ise sinyalin yükselme süresidir (rise time). Pratikte 3.3\text{ V} beslemeli ESP32 sistemlerinde standart 100\text{ kHz} veya 400\text{ kHz} hızlar için 2.2\text{ k}\Omega ile 4.7\text{ k}\Omega arasındaki dirençler en kararlı sinyal formunu sunar. Birçok hazır sensör modülünde (örn. BME280, MPU6050) bu pull-up dirençleri PCB üzerinde yerleşik olarak bulunur, bu yüzden harici direnç eklenmesine gerek kalmaz.

ESP32 Donanımsal I2C Portları ve Pinleri

ESP32 SoC bünyesinde I2C0 ve I2C1 olmak üzere iki adet tamamen bağımsız donanımsal I2C kontrolörü barındırır. Bu kontrolörler donanımsal filtreleme yeteneklerine sahiptir ve hat gürültülerini otomatik olarak süzerler. Donanım seviyesinde şu hız modları desteklenir:

  • Standart Mod (Standard-mode): 100\text{ Kbit/sn} haberleşme hızı.
  • Hızlı Mod (Fast-mode): 400\text{ Kbit/sn} haberleşme hızı (en yaygın kullanılan kararlı sınır).
  • Hızlı Mod Plus (Fast-mode Plus): 1\text{ Mbit/sn} hıza kadar destek sunar.

Arduino Core kütüphanesini kullanırken ESP32 için varsayılan donanımsal I2C pin tanımlamaları şu şekildedir:

I2C SinyaliVarsayılan GPIO (ESP32)Sensör Pin Adı (Örn: BME280)
SDA (Data)GPIO 21SDA / SDI / SDX
SCL (Clock)GPIO 22SCL / SCK / SCX
GND (Toprak)GNDGND
VCC (Besleme)3.3VVCC / VIN (3.3V)
ESP32 I2C Kullanımı - BME280
ESP32 I2C Kullanımı – BME280

Dahili donanımsal I2C birimlerini kullanırken her iki port da (I2C0 ve I2C1) ESP32’nin dahili GPIO matrisi sayesinde herhangi bir çakışma olmadan başka dijital pinlere de yönlendirilebilir. ESP32’nin tüm genel amaçlı giriş/çıkış sınırlamalarını öğrenmek için kapsamlı ESP32 Pin Referansı Kılavuzumuzu inceleyebilirsiniz.

ESP32 I2C Adres Tarama Kod Uygulaması

I2C veri yolunda haberleşebilmek için her slave cihazın benzersiz bir 7-bitlik oltalık (hexadecimal) adresi bulunmalıdır. Master cihaz (ESP32), veri çerçevesini gönderirken ilk byte içerisinde bu adresi yayınlar ve sadece ilgili adrese sahip cihaz veri yoluna yanıt döner. Çoğu sensörün adresi veri sayfasında (datasheet) yazar, fakat bilinmeyen veya klon modüllerde adresi tespit etmek için I2C adres tarayıcı yazılımı çalıştırılmalıdır.

Sensörlerinizi varsayılan I2C pinlerine (SDA: GPIO 21, SCL: GPIO 22) bağladıktan sonra aşağıdaki tarama kodunu yükleyip seri port ekranını 115200\text{ baud} hızında izleyebilirsiniz:

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  while (!Serial); // USB bağlantısının kararlı hale gelmesini bekle
  Serial.println("\n--- ESP32 I2C Adres Tarayıcı Başlatılıyor ---");
  
  // Varsayılan I2C veri yolunu başlat
  Wire.begin(); 
}

void loop() {
  byte hata, adres;
  int cihazSayisi = 0;

  Serial.println("I2C hattı taranıyor...");

  for (adres = 1; adres < 127; adres++) {
    // Verilen adres üzerinden boş bir yazma isteği göndererek cihazın varlığını sorgula
    Wire.beginTransmission(adres);
    hata = Wire.endTransmission();

    if (hata == 0) {
      Serial.print("Cihaz bulundu! Adres: 0x");
      if (adres < 16) Serial.print("0");
      Serial.print(adres, HEX);
      Serial.println("");
      cihazSayisi++;
    } 
    else if (hata == 4) {
      Serial.print("Bilinmeyen hata! Adres: 0x");
      if (adres < 16) Serial.print("0");
      Serial.println(adres, HEX);
    }
  }

  if (cihazSayisi == 0) {
    Serial.println("I2C veri yolunda hiçbir cihaz bulunamadı.\n");
  } else {
    Serial.println("Tarama tamamlandı.\n");
  }

  delay(5000); // 5 saniyede bir yeniden tara
}
Seri Port Ekranı I2C Tarama Sonuçları

ESP32’de Varsayılan I2C Pinlerini Değiştirme ve Özelleştirme

ESP32’de projelerin donanımsal yerleşim kısıtlamalarına göre varsayılan pinleri değiştirmek oldukça kolaydır. Arduino Wire.h kitaplığı ile özel I2C pinleri tanımlamak için şu fonksiyon çağrılır:

Wire.begin(SDA_PIN, SCL_PIN, saat_frekansi);

Ancak Adafruit veya SparkFun gibi popüler üçüncü parti sensör kütüphaneleri kullanılırken, sadece Wire.begin() fonksiyonunu özelleştirmek yetmez. Çünkü kütüphaneler kendi içlerinde varsayılan Wire nesnesini çağırarak pin ayarlarınızın üzerine yazabilir. Bunu aşmak için, ilgili kütüphanelerin yapıcı fonksiyonlarına (constructor) özel olarak yapılandırılmış bir TwoWire referansı geçilmelidir.

Adafruit BME280 Kitaplığı TwoWire Parametre Geçişi

Örneğin, SDA olarak GPIO 33 ve SCL olarak GPIO 32 pinlerini kullanarak BME280 sıcaklık-nem sensöründen veri okumak için hazırlanmış profesyonel uygulama kodu şu şekildedir:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Özel I2C Pin Tanımlamaları
#define OZEL_SDA 33
#define OZEL_SCL 32

#define DENIZ_SEVIYESI_BASINC (1013.25)

// Donanımsal I2C0 portunu (0) kullanarak yeni bir TwoWire nesnesi oluştur
TwoWire ozelI2C = TwoWire(0);
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  while(!Serial);
  Serial.println("Özel I2C Pinli BME280 Testi Başlatılıyor...");

  // Özel I2C portunu tanımlanan pinler ve 100 kHz hızla başlat
  ozelI2C.begin(OZEL_SDA, OZEL_SCL, 100000);

  // BME280 nesnesine özel TwoWire hattımızın adresini (&ozelI2C) parametre olarak geçiyoruz
  bool durum = bme.begin(0x76, &ozelI2C);  
  if (!durum) {
    Serial.println("BME280 sensörü bulunamadı, bağlantıları kontrol edin!");
    while (1);
  }

  Serial.println("BME280 başarıyla başlatıldı.\n");
}

void loop() { 
  Serial.print("Sıcaklık = ");
  Serial.print(bme.readTemperature());
  Serial.println(" *C");
  
  Serial.print("Basınç = ");
  Serial.print(bme.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.print("Nem = ");
  Serial.print(bme.readHumidity());
  Serial.println(" %");

  Serial.println("-------------------------------------");
  delay(2000);
}
ESP32 ve BME280 Bağlantı Diyagramı

Ortak I2C Veri Yolunda Çoklu Cihaz Kullanımı

I2C adres tabanlı bir protokol olduğu için, veri hattına bağlı olan cihazların adresleri farklı olduğu sürece, tek bir SDA ve SCL hattına düzinelerce sensör ve ekran paralel olarak bağlanabilir.

Senaryo 1: Farklı Adreslere Sahip Cihazlar

Örneğin; bir adet 0.96\text{ inç} SSD1306 OLED ekran (0\times3C adresi) ile bir adet BME280 sensörünü (0\times76 adresi) aynı SDA ve SCL hattına bağlayıp tek veri yolu üzerinden çalıştırmak tamamen sorunsuzdur:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BME280.h>

#define EKRAN_GENISLIK 128
#define EKRAN_YUKSEKLIK 64

// Donanımsal varsayılan Wire referansıyla ekran nesnesi oluştur
Adafruit_SSD1306 ekran(EKRAN_GENISLIK, EKRAN_YUKSEKLIK, &Wire, -1);
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  
  // Varsayılan I2C hattını başlat
  Wire.begin();

  // Ekranı 0x3C adresiyle başlat
  if(!ekran.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 OLED ekran başlatılamadı!"));
    for(;;);
  }
  
  // Sensörü 0x76 adresiyle başlat
  if (!bme.begin(0x76)) {
    Serial.println("BME280 sensörü başlatılamadı!");
    while (1);
  }
  
  ekran.clearDisplay();
  ekran.setTextColor(WHITE);
  ekran.setTextSize(1);
}

void loop() {
  float temp = bme.readTemperature();
  float hum = bme.readHumidity();

  ekran.clearDisplay();
  ekran.setCursor(0, 0);
  ekran.println("--- SENSOR VERILERI ---");
  ekran.println("");
  ekran.print("Sicaklik: ");
  ekran.print(temp);
  ekran.println(" C");
  ekran.print("Nem: % ");
  ekran.print(hum);
  
  ekran.display();
  delay(1000);
}
ESP32 Ortak Veri Yolu OLED ve BME280 Projesi

Fiziksel bağlantı şemasında görüldüğü gibi, iki cihazın SDA ve SCL hatları ESP32’nin aynı pinlerine paralel olarak birleştirilmiştir:

ESP32 Ortak Hat I2C Cihaz Şeması

Senaryo 2: Aynı Adrese Sahip Birden Fazla Cihaz (Çakışma Çözümleri)

Eğer projenizde aynı I2C adresine sahip iki adet SSD1306 OLED ekran veya birden fazla BME280 sensörü bağlamanız gerekirse, veri yolunda çakışma (address collision) meydana gelir. Master cihaz çağrı yaptığında iki cihaz da aynı anda hatta yanıt vereceğinden sinyaller bozulur. Bu problemi aşmak için üç temel endüstriyel çözüm bulunur:

1. Donanımsal Adres Seçim Pinlerini Değiştirmek

Birçok I2C modülünde, adres seçimi yapabilmeniz için lehim köprüleri veya adres pini (SDO/ADDR) bulunur. Örneğin, SSD1306 OLED ekranların arkasında yer alan adres seçici SMD direncinin konumu değiştirilerek (lehim köprüsü aktarılarak) ekranın adresi kolayca 0\times3C‘den 0\times3D‘ye değiştirilebilir. Bu sayede aynı veri yoluna iki ekran çakışmasız bağlanabilir.

OLED SSD1306 Donanımsal Adres Seçim Direnci

2. Donanımsal I2C Çoklayıcı (Multiplexer) Kullanmak

İkiden fazla aynı adresli cihazı bağlamak gerektiğinde veya donanımsal adres seçimi sunmayan sensörlerde en kesin çözüm TCA9548A 8-Kanal I2C Multiplexer entegresi kullanmaktır. Bu entegre, master tarafından gönderilen bir komut baytı doğrultusunda kendi arkasındaki 8 farklı alt I2C veri yolunu (SD0/SC0 – SD7/SC7) dinamik olarak ana I2C hattına bağlar. Böylece aynı adrese sahip 8 adet sensör çakışmasız kontrol edilebilir.

TCA9548A 8-Kanal I2C Multiplexer Entegresi

3. ESP32’nin Çift Donanımsal I2C Portunu Kullanmak

Harici bir entegre (multiplexer) satın almadan, ESP32’nin dahili çift donanımsal denetleyicisini (I2C0 ve I2C1) devreye alarak aynı adrese sahip iki adet sensörü tamamen izole bağımsız iki veri yolu üzerinden sürebilirsiniz. Bu yöntem hem maliyeti düşürür hem de veri yollarının elektriksel yükünü ikiye bölerek sinyal bütünlüğünü korur.

ESP32 Çift Donanımsal I2C Veri Yolu Şeması

ESP32 Çift I2C Veri Yolu Uygulama Projesi

Bu uygulamamızda, aynı oltalık adrese (0\times76) sahip iki adet BME280 sensörünü, ESP32’nin iki bağımsız I2C donanım birimi (I2C0 ve I2C1) üzerinden ayrı ayrı bağlayacağız. Birinci sensörü GPIO 27/26 pinlerine, ikinci sensörü ise GPIO 33/32 pinlerine bağlayacağız.

ESP32 Çift BME280 Bağlantı Diyagramı

Ardunio Çift I2C Kodu

Çift bağımsız TwoWire nesnesi oluşturarak her iki sensörü de kendi donanımsal hatlarından okuyan profesyonel kod yapısı şu şekildedir:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// I2C Veri Yolu 1 Pinleri (I2C0 Birimi)
#define SDA_1 27
#define SCL_1 26

// I2C Veri Yolu 2 Pinleri (I2C1 Birimi)
#define SDA_2 33
#define SCL_2 32

// İki bağımsız donanımsal TwoWire nesnesi oluşturuluyor
TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);

// Sensör kütüphanesi örnekleri
Adafruit_BME280 bme1;
Adafruit_BME280 bme2;

void setup() {
  Serial.begin(115200);
  while(!Serial);
  Serial.println("Çift Donanımsal I2C Başlatılıyor...");

  // İki bağımsız I2C veri yolunu farklı pinler ve hızlarda başlat
  I2Cone.begin(SDA_1, SCL_1, 100000); 
  I2Ctwo.begin(SDA_2, SCL_2, 100000);

  // 1. Sensörü 1. I2C hattından (I2Cone) başlat
  bool durum1 = bme1.begin(0x76, &I2Cone);  
  if (!durum1) {
    Serial.println("1. Sensör (BME1) bulunamadı, bağlantıları inceleyin!");
    while (1);
  }
  
  // 2. Sensörü 2. I2C hattından (I2Ctwo) başlat
  bool durum2 = bme2.begin(0x76, &I2Ctwo);  
  if (!durum2) {
    Serial.println("2. Sensör (BME2) bulunamadı, bağlantıları inceleyin!");
    while (1);
  }

  Serial.println("Her iki BME280 sensörü de başarıyla başlatıldı.\n");
}

void loop() { 
  // 1. Sensör Verilerini Oku (I2C0 Portu)
  Serial.print("[BME1] Sıcaklık: ");
  Serial.print(bme1.readTemperature());
  Serial.print(" *C\t Nem: % ");
  Serial.print(bme1.readHumidity());
  Serial.print("\tBasınç: ");
  Serial.print(bme1.readPressure() / 100.0F);
  Serial.println(" hPa");

  // 2. Sensör Verilerini Oku (I2C1 Portu)
  Serial.print("[BME2] Sıcaklık: ");
  Serial.print(bme2.readTemperature());
  Serial.print(" *C\t Nem: % ");
  Serial.print(bme2.readHumidity());
  Serial.print("\tBasınç: ");
  Serial.print(bme2.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.println("-----------------------------------------------------------------");
  delay(4000); // 4 saniyede bir verileri güncelle
}

Pratik Alternatif: Wire ve Wire1 Nesneleri

Yukarıdaki gibi sıfırdan TwoWire nesneleri tanımlamak yerine, Arduino Core içerisinde önceden tanımlanmış hazır Wire ve Wire1 nesnelerinden de faydalanabilirsiniz:

  • Wire nesnesi varsayılan GPIO 21 ve GPIO 22 pinlerini kullanarak donanımsal I2C0 portunu arka planda otomatik yönetir.
  • Wire1 nesnesi ise, parametre olarak geçeceğiniz özel pinler ve frekans doğrultusunda donanımsal I2C1 portunu yönetir.
Wire ve Wire1 Kütüphane Yönlendirme Şeması

Bu alternatif yöntemde, setup fonksiyonu içindeki başlatma bloğu şu pratik yapıya kavuşur:

void setup() {
  Serial.begin(115200);

  // İlk veri yolunu varsayılan pinlerle (GPIO 21/22) başlat
  Wire.begin(); 
  
  // İkinci veri yolunu özel pinlerle (GPIO 33/32) başlat
  Wire1.begin(33, 32, 100000); 

  // Sensörleri ilgili donanım nesnelerine bağla
  bme1.begin(0x76, &Wire);
  bme2.begin(0x76, &Wire1);
}

Hangi yöntemi seçerseniz seçin, her iki sensörün okumaları da bağımsız olarak işlemciye aktarılacaktır. Seri port çizicisinde veya terminal ekranında alacağınız kusursuz çıktı şu şekilde olacaktır:

Seri Port Ekranı Çift BME280 Veri Çıktısı

Özetle; ESP32’nin çift donanımsal I2C kontrolörü ve zengin pin atama kabiliyetleri, karmaşık gömülü sistem sensör ağlarında ek donanım maliyetleri olmadan şık, esnek ve yüksek sinyal kalitesine sahip profesyonel çözümler geliştirmenizi sağlar.

Örnek bir esp32 veri sayfasına buradan ulaşabilirsiniz.

Yorum yapma özelliği, forum tarafından gelen istek sebebiyle kapatılmıştır. Lütfen tartışmalar ve sorularınız için topluluk forumumuza katılın.