Gömülü sistem projelerinde ortam sıcaklığı, nem, basınç, ivme ve manyetik alan gibi fiziksel büyüklükleri ölçmek için kullanılan dijital sensörlerin büyük çoğunluğu iki telli I2C (Inter-Integrated Circuit) veri yolunu kullanır. ESP32 mikrodenetleyicisinde ESP-IDF I2C sensör okuma mimarisi; donanımsal I2C kontrolcüsü, kuyruk tabanlı komut bağlama (command link) ve kesme destekli veri transferi sayesinde güvenilir ve endüstriyel kalitede bir iletişim sağlar.
Bu rehberde; ESP-IDF driver/i2c.h kütüphanesini, I2C Master yapılandırmasını, sensör register adreslerine yazma ve okuma işlemlerini ve popüler MPU6050 ivmeölçer / jiroskop sensörü için sıfırdan sürücü yazımını adım adım inceleyeceğiz.
I2C Veri Yolu Temelleri ve ESP32 Donanımı
I2C protokolü iki çift yönlü açık kolektör (open-drain) hat üzerinden çalışır:
- SDA (Serial Data Line): Veri hattı.
- SCL (Serial Clock Line): Saat sinyali hattı.
Her iki hatta da 3.3V seviyesine bağlı 4.7kΩ veya 10kΩ pull-up dirençleri bulunmalıdır (ESP32 dahili pull-up dirençlerine sahip olsa da harici dirençler yüksek hızlarda sinyal bütünlüğü için şarttır).
ESP32 üzerinde 2 adet bağımsız donanımsal I2C kontrolcüsü bulunur (I2CNUM0 ve I2CNUM1). Herhangi bir GPIO pini matris üzerinden SDA ve SCL olarak atanabilir.
| Parametre | Standart Mod (Standard) | Hızlı Mod (Fast Mode) |
|---|---|---|
| Maksimum Hız | 100 kHz (100000) | 400 kHz (400000) |
| Kablo Mesafesi | ~1-2 metre | ~30-50 cm |
| Tipik Sensörler | RTC (DS3231), EEPROM | IMU (MPU6050), BME280, OLED Ekranlar |
Adım 1: I2C Master Yapılandırması
I2C donanımını başlatmak için i2cconfigt yapısı doldurulur ve i2cparamconfig ile i2cdriverinstall çağrılır:
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
#define I2C_MASTER_SCL_IO 22 /*!< SCL pini */
#define I2C_MASTER_SDA_IO 21 /*!< SDA pini */
#define I2C_MASTER_NUM I2C_NUM_0 /*!< I2C port numarası */
#define I2C_MASTER_FREQ_HZ 400000 /*!< I2C saat frekansı (400kHz) */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< Master için TX buffer gerekmez */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< Master için RX buffer gerekmez */
#define I2C_MASTER_TIMEOUT_MS 1000
static const char *TAG = "I2C_MASTER";
esp_err_t i2c_master_init(void) {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
.clk_flags = 0,
};
esp_err_t err = i2c_param_config(I2C_MASTER_NUM, &conf);
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2C parametre hatası: %s", esp_err_to_name(err));
return err;
}
return i2c_driver_install(I2C_MASTER_NUM, conf.mode,
I2C_MASTER_RX_BUF_DISABLE,
I2C_MASTER_TX_BUF_DISABLE, 0);
}
Adım 2: Register Yazma ve Okuma Fonksiyonları
Bir I2C sensörden veri okumak için standart akış:
STARTbiti gönder.- Sensör I2C adresini yazma bitiyle gönder (
(ADDR << 1) | I2CMASTERWRITE). - Okunmak istenen iç register adresini gönder.
- Yeniden
START(Repeated Start) gönder. - Sensör adresini okuma bitiyle gönder (
(ADDR << 1) | I2CMASTERREAD). - Baytları oku (son baytta
NACK, öncekilerdeACKgönder). STOPbiti ile hattı serbest bırak.
// Sensörün belirli bir register'ına 1 bayt veri yaz
esp_err_t i2c_write_reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_write_byte(cmd, data, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
i2c_cmd_link_delete(cmd);
return ret;
}
// Sensörün belirli bir register'ından N bayt veri oku
esp_err_t i2c_read_reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data_buf, size_t len) {
if (len == 0) return ESP_OK;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
// Önce hangi register'ı okuyacağımızı söyle
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
// Repeated Start ile okuma moduna geç
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
if (len > 1) {
i2c_master_read(cmd, data_buf, len - 1, I2C_MASTER_ACK);
}
i2c_master_read_byte(cmd, data_buf + len - 1, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
i2c_cmd_link_delete(cmd);
return ret;
}
Adım 3: MPU6050 İvme ve Jiroskop Okuma Uygulaması
MPU6050 varsayılan I2C adresi 0x68‘dir. Açılışta uyku (sleep) modunda başlar; çalıştırmak için PWRMGMT1 (0x6B) register’ına 0x00 yazılmalıdır:
#define MPU6050_ADDR 0x68
#define MPU6050_WHO_AM_I 0x75
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_ACCEL_XOUT_H 0x3B
void mpu6050_task(void *pvParameters) {
uint8_t who_am_i = 0;
// Kimlik doğrulama
ESP_ERROR_CHECK(i2c_read_reg(MPU6050_ADDR, MPU6050_WHO_AM_I, &who_am_i, 1));
ESP_LOGI(TAG, "MPU6050 Bulundu! WHO_AM_I: 0x%02X", who_am_i);
// Uyku modundan çıkar
ESP_ERROR_CHECK(i2c_write_reg(MPU6050_ADDR, MPU6050_PWR_MGMT_1, 0x00));
uint8_t raw_data[14]; // 6 bayt ivme, 2 bayt sıcaklık, 6 bayt jiroskop
int16_t accel_x, accel_y, accel_z;
while (1) {
// 14 baytı ardışık oku (Burst read)
esp_err_t ret = i2c_read_reg(MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, raw_data, 14);
if (ret == ESP_OK) {
accel_x = (raw_data[0] << 8) | raw_data[1];
accel_y = (raw_data[2] << 8) | raw_data[3];
accel_z = (raw_data[4] << 8) | raw_data[5];
// G birimine çevir (+/- 2g sklasında 1g = 16384 LSB)
float ax = (float)accel_x / 16384.0f;
float ay = (float)accel_y / 16384.0f;
float az = (float)accel_z / 16384.0f;
ESP_LOGI(TAG, "İvme -> X: %.2f g, Y: %.2f g, Z: %.2f g", ax, ay, az);
} else {
ESP_LOGW(TAG, "Veri okuma hatası: %s", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Hz okuma frekansı
}
}
FreeRTOS Görev Entegrasyonu (app_main)
FreeRTOS Görevleri ve Kuyruklar rehberimizde incelediğimiz gibi, sensör okuma işlemini ana döngüyü bloklamayan ayrı bir thread (task) olarak başlatırız:
void app_main(void) {
ESP_LOGI(TAG, "I2C başlatılıyor...");
ESP_ERROR_CHECK(i2c_master_init());
// Sensör task'ını Core 1 üzerinde başlat
xTaskCreatePinnedToCore(mpu6050_task, "mpu6050_task", 4096, NULL, 5, NULL, 1);
}
Hata Ayıklama İpuçları (Troubleshooting)
ESPERRTIMEOUTHatası: I2C hatlarındaki pull-up dirençleri eksik veya çok zayıftır. 4.7kΩ harici dirençler ekleyin.- I2C Aygıt Tarayıcı (Bus Scanner): Sensör adresinden emin değilseniz 0x01’den 0x7F’e kadar tüm adreslere boş bir yazma komutu göndererek
ESP_OKdönen adresi bularak hattı test edin. - Kablo Boyu: Kablolar 30 cm’den uzunsa parazitleri önlemek için hızı 100 kHz (
I2CMASTERFREQ_HZ = 100000) değerine düşürün.
Özet
ESP-IDF’in güçlü I2C sürücüsü ile her türlü dijital sensörü register düzeyinde okuyabilirsiniz. Temiz readreg / writereg soyutlamaları ve FreeRTOS görev entegrasyonu ile profesyonel IoT ölçüm cihazları geliştirebilirsiniz.