1. #!trpst#trp-gettext data-...
  2. #!trpst#trp-gettext data-...
  3. ESP-IDF / ESP32
  4. Giriş
  5. ESP-IDF ile I2C Sensör Okuma ve Sürücü Yazımı (BME280 / MPU6050)

ESP-IDF ile I2C Sensör Okuma ve Sürücü Yazımı (BME280 / MPU6050)

Print

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:

  1. SDA (Serial Data Line): Veri hattı.
  2. 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.

ParametreStandart Mod (Standard)Hızlı Mod (Fast Mode)
Maksimum Hız100 kHz (100000)400 kHz (400000)
Kablo Mesafesi~1-2 metre~30-50 cm
Tipik SensörlerRTC (DS3231), EEPROMIMU (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ış:

  1. START biti gönder.
  2. Sensör I2C adresini yazma bitiyle gönder ((ADDR << 1) | I2CMASTERWRITE).
  3. Okunmak istenen iç register adresini gönder.
  4. Yeniden START (Repeated Start) gönder.
  5. Sensör adresini okuma bitiyle gönder ((ADDR << 1) | I2CMASTERREAD).
  6. Baytları oku (son baytta NACK, öncekilerde ACK gönder).
  7. STOP biti 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)

  1. ESPERRTIMEOUT Hatası: I2C hatlarındaki pull-up dirençleri eksik veya çok zayıftır. 4.7kΩ harici dirençler ekleyin.
  2. 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_OK dönen adresi bularak hattı test edin.
  3. 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.

Tags , , , , ,

How can we help?