00 I²C protokolü temelleri
I²C (Inter-Integrated Circuit) — Philips'in 1982'de geliştirdiği, sadece iki tel gerektiren senkron seri protokol.
I²C'nin temel özellikleri onu sensör haberleşmesinde popüler yapan şeydir: 7-bit adresleme ile 112 farklı cihaz (10-bit ile 1008 cihaza kadar) aynı iki teli paylaşabilir. Her işlem master tarafından başlatılır; slave'ler sadece yanıt verir.
Master ─────────────────────────────── Slave 1 (0x48)
│ Slave 2 (0x76)
SDA ───┤─────────────────────────────── Slave 3 (0x3C)
SCL ───┘
│←── 4.7kΩ pull-up ───→ VCC
SDA ve SCL hatları
Sadece iki sinyal söz konusudur:
Pull-up dirençler
I²C open-drain protokolüdür: her iki hat da pull-up direnci üzerinden VCC'ye bağlanır. Direnç değeri bus kapasitansına ve hıza bağlıdır:
| Hız | Önerilen R | Maks. kapasitans |
|---|---|---|
| Standard Mode (100 kHz) | 4.7 kΩ | 400 pF |
| Fast Mode (400 kHz) | 2.2 kΩ | 400 pF |
| Fast-Plus (1 MHz) | 1 kΩ | 550 pF |
Start / Stop koşulları ve ACK/NACK
Bir I²C transaction şu adımlarla ilerler:
- START condition: SCL yüksekken SDA düşer.
- 7-bit slave adresi gönderilir, ardından 1 bit R/W (0=yaz, 1=oku).
- Slave adresi tanırsa ACK (SDA'yı düşürür), tanımazsa NACK gönderir.
- Veri baytları gönderilir; her bayt sonrası alıcıdan ACK beklenir.
- STOP condition: SCL yüksekken SDA yükselir.
- Tekrarlanan START (Repeated START) ile STOP vermeden yeni transaction başlatılabilir — bu özellikle register oku işlemlerinde kullanılır.
Repeated START çok önemlidir: I²C cihazlarının çoğu bir register'dan okumak için önce register adresini yazar (write transaction), ardından Repeated START ile okuma başlatır. Bu iki ayrı mesajı tek transaction içinde birleştirir.
Multi-master
I²C multi-master destekler: birden fazla master aynı anda bus kullanmak isterse arbitration mekanizması devreye girer. SDA'ya logic-1 süren master, diğerinin logic-0 sürdüğünü fark eder ve bus'ı bırakır. Gömülü Linux'ta genellikle tek master kullanılır.
01 Linux I²C subsystem
Linux'ta I²C desteği üç katmandan oluşur: donanım adaptörü (adapter), bağlı cihaz (client) ve driver.
Userspace /dev/i2c-0 /dev/i2c-1 ...
│
Kernel i2c-dev (character device driver)
│
i2c_adapter (bcm2835_i2c, i2c-imx, i2c-mv64xxx ...)
│
Hardware (SoC I²C controller register'ları)
Kernel yapılandırması
I²C userspace erişimi için i2c-dev modülü yüklü olmalıdır. Raspberry Pi ve BeagleBone gibi kartlarda genellikle varsayılan olarak aktifdir.
# menuconfig / defconfig içinde gerekli seçenekler:
CONFIG_I2C=y
CONFIG_I2C_CHARDEV=y # /dev/i2c-N erişimi için
CONFIG_I2C_BCM2835=y # Raspberry Pi SoC adaptörü (örnek)
CONFIG_I2C_IMX=y # NXP i.MX adaptörü (örnek)
# Modül olarak da derlenebilir (=m):
CONFIG_I2C_CHARDEV=m
# i2c-dev modülünü yükle (=m ise):
sudo modprobe i2c-dev
# Mevcut I²C adaptörlerini gör:
ls /dev/i2c-*
# /dev/i2c-0 /dev/i2c-1
# i2c-dev karakteristik cihaz bilgisi:
ls -la /dev/i2c-1
# crw-rw---- 1 root i2c 89, 1 Jan 1 00:00 /dev/i2c-1
# Kullanıcıyı i2c grubuna ekle (her seferinde sudo kullanmamak için):
sudo usermod -aG i2c $USER
# Oturumu yeniden aç veya: newgrp i2c
Device Tree bağlaması
SoC'ye bağlı I²C slave'ler device tree'de tanımlanır. Bu sayede kernel boot sırasında ilgili driver'ı otomatik yükler.
&i2c1 {
clock-frequency = <400000>; /* Fast Mode: 400 kHz */
status = "okay";
bme280: bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>; /* 7-bit I²C adresi */
};
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
pagesize = <32>;
};
};
i2c_adapter: SoC'nin donanım I²C controller'ını temsil eder. i2c_client: bus'a bağlı bir slave cihazı temsil eder (adres + adapter bilgisi içerir). i2c_driver: belirli bir donanım modelini destekleyen kernel driver yapısıdır.
02 i2c-tools kurulumu ve i2cdetect
i2c-tools paketi userspace'den I²C bus'ına doğrudan erişimi sağlayan araç seti içerir.
# Debian / Ubuntu / Raspberry Pi OS:
sudo apt install i2c-tools
# Yocto (meta-oe layer):
# IMAGE_INSTALL += "i2c-tools"
# Buildroot:
# BR2_PACKAGE_I2C_TOOLS=y
# Kurulu araçları kontrol et:
which i2cdetect i2cget i2cset i2cdump i2ctransfer
i2cdetect ile bus tarama
# Sistemdeki tüm I²C bus'ları listele:
i2cdetect -l
# i2c-0 i2c bcm2835 (i2c@7e205000) I2C adapter
# i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
# Bus 1'deki tüm cihazları tara:
sudo i2cdetect -y 1
Tipik i2cdetect -y 1 çıktısı:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
Bu çıktıda 0x3C adresinde bir SSD1306 OLED ekran, 0x76 adresinde ise BME280 sensörü görünmektedir. -- o adreste cihaz olmadığını gösterir.
i2cdetect tarama sırasında her adrese yazma denemesi yapar. Bazı cihazlar (özellikle EEPROM ve motor sürücüler) bu yazma probundan etkilenebilir. Cihaz tipini biliyorsan -r seçeneğini kullan.
03 i2cget ve i2cset
Tek register okuma ve yazma işlemleri için kullanılan komutlar. SMBus protokol modlarını destekler.
i2cget — register okuma
# Sözdizimi: i2cget [-y] BUS ADDR [REG [MODE]]
# BME280 (0x76) chip ID register'ını oku (0xD0 = 0x60 döndürür):
sudo i2cget -y 1 0x76 0xD0
# 0x60
# Word (16-bit, little-endian) okuma:
sudo i2cget -y 1 0x48 0x00 w
# 0x7d00 ← TMP102 sıcaklık register'ı
# Block okuma (SMBus block read):
sudo i2cget -y 1 0x50 0x00 i
i2cset — register yazma
# Sözdizimi: i2cset [-y] BUS ADDR REG VALUE [MODE]
# BME280 reset register'ına yazma (0xB6 = soft reset):
sudo i2cset -y 1 0x76 0xE0 0xB6
# BME280 ctrl_meas register'ı: osrs_t=2 (2x), osrs_p=5 (16x), mode=3 (normal):
# osrs_t[7:5]=010, osrs_p[4:2]=101, mode[1:0]=11 → 0b01010111 = 0x57
sudo i2cset -y 1 0x76 0xF4 0x57
# 16-bit word yazma (word mode, little-endian):
sudo i2cset -y 1 0x48 0x01 0x6000 w
# Birden fazla bayt (I²C block write):
sudo i2cset -y 1 0x50 0x00 0xAB 0xCD 0xEF i
# -e: PEC (Packet Error Check) ile CRC doğrulama:
sudo i2cset -y -e 1 0x60 0x05 0x01
EEPROM'lara yazarken sayfa sınırlarına dikkat et. 24Cxx EEPROM'ların sayfa boyutu 8-128 bayt arasında değişir; sayfa sınırını aşan yazma işlemleri sayfa başına geri döner ve veri bozulur.
04 i2cdump — register haritası dökümü
Bir cihazın tüm register'larını tek seferde okuyarak görsel harita çıkaran araç.
# Sözdizimi: i2cdump [-y] BUS ADDR [MODE]
# BME280 tüm register'larını oku (byte mode):
sudo i2cdump -y 1 0x76
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
...
80: a0 6b 4d 67 32 00 4a 93 5a d7 d0 0b 2f fb cf 16 .mMg2.J.Z..../..
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
...
d0: 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 `...............
...
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 57 ...............W
f4: 57 00 00 b6 00 00 00 00 00 00 00 00 00 00 00 00 W...............
# 0xD0 = 0x60 → chip_id = BME280 doğrulandı
# 0xF4 = 0x57 → ctrl_meas: osrs_t=2, osrs_p=5, mode=normal
i2cdump çıktısında sol taraf onaltılık değer, sağ taraf ASCII karşılıklarıdır. Register adresi bilinen cihazlarda belirli satırları izlemek daha verimlidir: i2cdump -y 1 0x76 | grep "^d0" sadece 0xD0–0xDF satırını gösterir.
05 i2ctransfer — raw message gönderme
SMBus protokol kısıtlamalarını aşarak ham I²C mesajları gönderir. Combined message ve custom transaction'lar için kullanılır.
# Sözdizimi: i2ctransfer [-y] BUS DESC [DATA] [DESC [DATA] ...]
# DESC: {r|w}LENGTH[@ADDR]
# Tek write: BME280'e 0xF4 = 0x57 yaz:
sudo i2ctransfer -y 1 w2@0x76 0xF4 0x57
# Combined message (write + repeated start + read):
# BME280 register 0xF7'den 8 bayt oku (ham ADC verileri):
sudo i2ctransfer -y 1 w1@0x76 0xF7 r8@0x76
# 0x50 0xea 0x00 0x80 0xdc 0x00 0x6f 0xc0
# EEPROM 24C32: adres 0x0010'dan 4 bayt oku:
# Önce 2-bayt adres yaz (big-endian), sonra oku:
sudo i2ctransfer -y 1 w2@0x50 0x00 0x10 r4@0x50
# Birden fazla bağımsız transaction:
sudo i2ctransfer -y 1 w1@0x48 0x00 r2@0x48
i2ctransfer, SMBus olmayan saf I²C mesajları gönderebilir. Bu özellik şu durumlarda kritiktir:
- SMBus'ın desteklemediği uzunluklarda block transfer (33 bayttan fazla)
- 10-bit adresleme kullanan cihazlar
- Özel protokol gerektiren sensörler (combined message mantığı)
SMBus protokolü I²C'nin bir alt kümesidir. i2cget/i2cset SMBus komutlarını kullanırken, i2ctransfer I2C_RDWR ioctl'ini kullanarak ham mesaj gönderir. Mevcut fonksiyon setini i2cdetect -F 1 ile kontrol edebilirsin.
06 Python ile I²C: smbus2
smbus2 kütüphanesi Python'dan /dev/i2c-N arayüzüne doğrudan erişim sağlar. SMBus ve I²C combined message destekler.
pip3 install smbus2
# veya sistem paketi:
sudo apt install python3-smbus2
from smbus2 import SMBus, i2c_msg
# Bus 1'i aç (/dev/i2c-1)
with SMBus(1) as bus:
# Tek bayt okuma (SMBus read_byte_data):
chip_id = bus.read_byte_data(0x76, 0xD0)
print(f"Chip ID: 0x{chip_id:02X}") # 0x60
# Tek bayt yazma (SMBus write_byte_data):
bus.write_byte_data(0x76, 0xF4, 0x57)
# 16-bit word okuma (little-endian):
raw = bus.read_word_data(0x48, 0x00)
print(f"Word: 0x{raw:04X}")
# Block okuma — 6 bayt (register 0xF7'den):
data = bus.read_i2c_block_data(0x76, 0xF7, 6)
print(f"Raw ADC: {[hex(b) for b in data]}")
# Combined message (write + repeated start + read):
write_msg = i2c_msg.write(0x76, [0x88]) # register adresi
read_msg = i2c_msg.read(0x76, 24) # 24 bayt oku
bus.i2c_rdwr(write_msg, read_msg)
calib_raw = list(read_msg)
print(f"Calibration bytes: {calib_raw}")
Önemli SMBus2 metodları
07 Pratik: BME280 sensör okuma
Bosch BME280, tek çipte sıcaklık + nem + basınç ölçen popüler bir I²C sensörüdür. Register haritası ve kalibrasyon mekanizmasını anlayarak ham veriden fiziksel değer elde edelim.
BME280 register haritası (önemli register'lar)
| Register | Adı | Açıklama |
|---|---|---|
| 0xD0 | id | Chip ID: BME280=0x60, BMP280=0x58 |
| 0xE0 | reset | 0xB6 yazılırsa soft reset |
| 0xF2 | ctrl_hum | Nem oversampling (osrs_h[2:0]) |
| 0xF3 | status | measuring[3], im_update[0] |
| 0xF4 | ctrl_meas | osrs_t[7:5], osrs_p[4:2], mode[1:0] |
| 0xF5 | config | t_sb[7:5], filter[4:2], spi3w_en[0] |
| 0xF7–0xF9 | press_msb/lsb/xlsb | Ham basınç verisi (20-bit) |
| 0xFA–0xFC | temp_msb/lsb/xlsb | Ham sıcaklık verisi (20-bit) |
| 0xFD–0xFE | hum_msb/lsb | Ham nem verisi (16-bit) |
| 0x88–0x9F | calib00–17 | Sıcaklık/Basınç kalibrasyon verileri |
| 0xE1–0xE7 | calib26–32 | Nem kalibrasyon verileri |
import struct
from smbus2 import SMBus, i2c_msg
BME280_ADDR = 0x76
I2C_BUS = 1
def read_calibration(bus):
"""Kalibrasyon register'larını oku ve parse et."""
# 0x88'den 24 bayt: dig_T1..T3, dig_P1..P9
w = i2c_msg.write(BME280_ADDR, [0x88])
r = i2c_msg.read(BME280_ADDR, 24)
bus.i2c_rdwr(w, r)
raw = list(r)
calib = {}
calib['T1'] = struct.unpack_from('<H', bytes(raw[0:2]))[0]
calib['T2'] = struct.unpack_from('<h', bytes(raw[2:4]))[0]
calib['T3'] = struct.unpack_from('<h', bytes(raw[4:6]))[0]
calib['P1'] = struct.unpack_from('<H', bytes(raw[6:8]))[0]
calib['P2'] = struct.unpack_from('<h', bytes(raw[8:10]))[0]
calib['P3'] = struct.unpack_from('<h', bytes(raw[10:12]))[0]
calib['P4'] = struct.unpack_from('<h', bytes(raw[12:14]))[0]
calib['P5'] = struct.unpack_from('<h', bytes(raw[14:16]))[0]
calib['P6'] = struct.unpack_from('<h', bytes(raw[16:18]))[0]
calib['P7'] = struct.unpack_from('<h', bytes(raw[18:20]))[0]
calib['P8'] = struct.unpack_from('<h', bytes(raw[20:22]))[0]
calib['P9'] = struct.unpack_from('<h', bytes(raw[22:24]))[0]
# 0xA1: dig_H1
calib['H1'] = bus.read_byte_data(BME280_ADDR, 0xA1)
# 0xE1'den 7 bayt: dig_H2..H6
w2 = i2c_msg.write(BME280_ADDR, [0xE1])
r2 = i2c_msg.read(BME280_ADDR, 7)
bus.i2c_rdwr(w2, r2)
h = list(r2)
calib['H2'] = struct.unpack_from('<h', bytes(h[0:2]))[0]
calib['H3'] = h[2]
calib['H4'] = (h[3] << 4) | (h[4] & 0x0F)
calib['H5'] = (h[5] << 4) | (h[4] >> 4)
calib['H6'] = struct.unpack_from('<b', bytes([h[6]]))[0]
return calib
def compensate_temperature(adc_T, calib):
"""Bosch datasheet formülü (32-bit integer versiyonu)."""
var1 = ((adc_T >> 3) - (calib['T1'] << 1)) * calib['T2'] >> 11
var2 = ((adc_T >> 4) - calib['T1'])
var2 = (var2 * var2 >> 12) * calib['T3'] >> 14
t_fine = var1 + var2
T = (t_fine * 5 + 128) >> 8
return T / 100.0, t_fine
def compensate_humidity(adc_H, t_fine, calib):
x = t_fine - 76800
x = (adc_H << 14) - (calib['H4'] << 20) - calib['H5'] * x
x = (x + 16384) >> 15
x = x * (((((((x * calib['H6']) >> 10) *
(((x * calib['H3']) >> 11) + 32768)) >> 10) + 2097152) *
calib['H2'] + 8192) >> 14)
x = x - (((((x >> 15) ** 2) >> 7) * calib['H1']) >> 4)
x = max(0, min(x, 419430400))
return (x >> 12) / 1024.0
with SMBus(I2C_BUS) as bus:
# Chip ID kontrol:
chip_id = bus.read_byte_data(BME280_ADDR, 0xD0)
assert chip_id == 0x60, f"BME280 değil: 0x{chip_id:02X}"
# Kalibrasyon oku:
calib = read_calibration(bus)
# Forced mode: tek ölçüm al
bus.write_byte_data(BME280_ADDR, 0xF2, 0x01) # ctrl_hum: osrs_h=1
bus.write_byte_data(BME280_ADDR, 0xF4, 0x25) # osrs_t=1, osrs_p=1, forced
import time; time.sleep(0.01) # ölçüm tamamlanmasını bekle
# Ham verileri oku (0xF7'den 8 bayt):
w = i2c_msg.write(BME280_ADDR, [0xF7])
r = i2c_msg.read(BME280_ADDR, 8)
bus.i2c_rdwr(w, r)
d = list(r)
adc_P = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4)
adc_T = (d[3] << 12) | (d[4] << 4) | (d[5] >> 4)
adc_H = (d[6] << 8) | d[7]
temp, t_fine = compensate_temperature(adc_T, calib)
hum = compensate_humidity(adc_H, t_fine, calib)
print(f"Sıcaklık : {temp:.2f} °C")
print(f"Nem : {hum:.2f} %")
08 Troubleshooting
I²C sorunlarının büyük çoğunluğu pull-up değerleri, clock stretching veya bus stuck durumundan kaynaklanır.
Bus stuck — SDA veya SCL sürekli düşük
Ani güç kesintisi veya hatalı transaction sonrası slave SDA'yı düşük tutabilir. Çözüm:
# 1) Hata mesajını kontrol et:
dmesg | grep i2c
# [ 142.3] i2c i2c-1: adapter timeout
# [ 142.3] i2c i2c-1: sendbytes: error -11
# 2) 9 dummy clock pulse ile slave'i serbest bırak (yazılımsal):
sudo i2cdetect -y 1 # bus'ı test et
# 3) I²C adaptörünü kernel'den sıfırla (BCM2835 örneği):
echo "3e804000.i2c" | sudo tee /sys/bus/platform/drivers/i2c-bcm2835/unbind
echo "3e804000.i2c" | sudo tee /sys/bus/platform/drivers/i2c-bcm2835/bind
Clock stretching sorunu
Bazı SoC'ler (özellikle Raspberry Pi BCM2835) clock stretching'i yanlış uygular. Slave yavaşsa SCL'yi düşük tutarak zaman kazanır; BCM2835 bu durumu yanlış işler. Çözüm: I²C hızını düşür.
# I²C hızını 50 kHz'e düşür (clock stretching sorunları için):
dtparam=i2c_arm=on,i2c_arm_baudrate=50000
Oscilloscope ile I²C decode
Logic analyzer veya oscilloscope ile I²C trafiği analiz edilebilir:
- SDA ve SCL kanallarını ayrı ayrı bağla.
- Trigger: SCL düşen kenar (falling edge) veya SDA düşen kenar (SCL yüksekken = START).
- Sigrok / PulseView ile "I²C" protocol decoder seç: adres, R/W biti, ACK/NACK, data baytları otomatik decode edilir.
- ACK yerine NACK görünüyorsa: yanlış adres, cihaz powered off veya bus stuck.
| Belirti | Olası neden | Çözüm |
|---|---|---|
| i2cdetect çıktısı tamamen boş (--) | Pull-up yok, cihaz beslenmiyor, SDA/SCL ters | Bağlantıları kontrol et, 4.7kΩ pull-up ekle |
| UU gösterimi (cihaz meşgul) | Kernel driver cihazı zaten açmış | Kernel driver'ı devre dışı bırak ya da i2ctransfer kullan |
| NACK alınıyor | Yanlış adres veya cihaz powered off | Datasheet'ten adresi doğrula, ADDR pinini kontrol et |
| Veri bozuk / CRC hatası | Bus hızı çok yüksek, pull-up yetersiz | Hızı düşür, pull-up direncini küçült |
| Intermittent hatalar | EMI, uzun kablo, kapasitif yük | Kablo uzunluğunu kıs, buffer IC kullan |