STM32 ile I2C kullanımının mantığı (kod içermez)
I2C ile iletişimde 4 kablo bağlantısı gerekir. Bunlar VCC - GND - SCL - SDA dır. Veri iletimi tek hat üzerinden birden fazla cihazın bağlanabilmesi ile gerçekleştirilir. SCL hattı ise saat sinyali taşımaktadır. UART ın aksine tek hat üzerinden birden fazla cihaz iletişim kurabilir, birden fazla cihazın birbirlerini dinlemesi ile bu düzen sağlanmış olunur.
Veri iletimi sırasında push-pull yerine open-drain kullanılır. Bunun nedeni ise push-pull ile veri iletiminde örneğin bir cihaz 0 göndermek için hattı 0 a çekmek isteyebilir. Aynı anda başka bir cihaz ise hattı 1 e çekmek isteyebilir. Bu durumda aynı anda iki cihaz hattı kontrol etmeye çalışır ve kısa devre oluşur.
Bu durumu çözmek için open-drain kullanılır. Bu yöntemde cihazlar hattı sadece Low a çekebilir, High a aktif olarak çekemez. Hattın High olması ise bir pull-up direnç sayesinde pasif olarak sağlanır. Cihazlar arasında henüz bir iletişim yokken bile hat sürekli high durumunda bekler (idle durumu). Bu idle durumunda hattın high olması diğer cihazlar için bir anlam ifade etmemektedir. Fakat sonrasında bir cihaz iletişim başlatmak istediğinde i2c protokolüne göre önce SCL high durumunda iken SDA hattını high dan Low a çeker. Bu diğer cihazlara bir iletişimin başladığının bilgisini verir. Sonrasında artık iletişim durumuna geçilir (idle durumu biter) ve artık her SCL darbesi ile veri iletimi SDA hattından yapılmış olunur. Bu süreçte artık SDA hattının high olması 1 i, Low olması 0 ı temsil etmektedir. Yani eski idle durumdakinin aksine artık high olmasının bir anlamı vardır. Bundan sonra iletişim kuran cihazlar hattı Low yaparak 0 gönderirler, hatta dokunmayarak (pull-up'ın hattı High'da tutmasına izin vererek) da 1 göndermiş olurlar.
I2C de ayrıca veri okuma ve yazma şu şekilde çalışır: gönderen taraf SCL hattı Low iken SDA hattında değişiklik yapabilir (bir sonraki biti hazırlar). Karşı taraf ise SCL hattı high iken SDA hattından o anki biti okur. Yani SCL Low iken hat "hazırlanır", SCL High iken hat "okunur".
İletişimi başlatma ve bitirme ise şu şekilde yapılmış olunur: START condition: SCL HIGH iken SDA, HIGH'dan LOW'a düşürülür. STOP condition: SCL HIGH iken SDA, LOW'dan HIGH'a yükseltilir.
Sonrasında ise artık iletişim kurmak isteyen cihaz hangi cihaz ile iletişim kurmak istiyorsa onun adresini gönderir. Sonrasında artık bu adrese sahip cihazdan ACK beklenir. İlk 8 bit şeklinde adres gönderildikten sonra 9. Clock ile ACK bilgisini alıcı cihaz (iletişim kurulmak istenen cihaz) gönderir. ACK göndermek için alıcı cihaz SDA yı Low yapar (9. Bit ile). Eğer bu alıcı cihazın adresi doğru ise ACK gönderilir. Eğer alıcı cihazın adresi yanlış ise bu sefer alıcı cihaz hatta hiç dokunmaz, hat pull-up sayesinde high olarak kalır, bu da "benim adresim bu değil" demenin dolaylı yoludur. Yani aslında hattaki bir gönderici cihaz bir adresi hatta gönderir, sonrasında hatta cihazlar bu veriyi SDA hattından dinler. Eğer bu yayınlanan adres bu cihazlardan birine aitse o zaman bu iletişim kurulmak istenen cihaz ACK gönderir. Eğer hatta gönderilen adrese uygun hiçbir cihaz yok ise o zaman hiçbir cihaz ACK göndermez, hat high olarak kalır. Bu durumda gönderici cihaz hatta iletişim kuracak kimse olmadığını veya gönderdiği adresin yanlış olduğunu öğrenmiş olur.
I2C - GPIO register'larının ayarlanması
İlk önce hangi port kullanılacaksa onun clock'u açılır:
- GPIOB->MODER |= ...
- Tabii ki bundan önce bu gpio portunun bağlı olduğu hattın clock'u açılmalıdır (AHB1 vs.)
Pinlerin modunun belirlenmesi:
- Pinlerin modu belirlenir, 4 farklı mod vardır: Alternate Fonk., Input, Output, Analog.
- Alternate Fonk. Modunda artık bu pinin gpio tarafından değil, başka bir birim tarafından kullanılacağı ayarlanmış olunur. I2C için AF olarak ayarlanmalıdır.
Output Type belirlenmesi:
- Bu ayar önemlidir çünkü önceden de belirttiğim gibi bu ayar ile open-drain veya push-pull ayarının belirlenmesi sağlanır. Open-drain olarak ayarlanmalıdır.
Output Speed belirlenmesi:
- Buna genelde çıkış hızı denir ama bu CPU ile alakalı değildir. Bu aslında transistörlerin ne kadar hızlı açılıp kapanacağıdır (slew rate). Pin 3.3V tan 0V a 20 ns de de geçebilir, 3 ns de de. I2C için genelde very high kullanılır.
İç pull-up, pull-down register'ının belirlenmesi:
- Bu ayar ile pinin boşta iken 0 ile 1 arasında belirsiz kalması engellenir, pin sabit bir değerde (3.3V veya 0V) tutulur. I2C hattında genelde bu iş için dahili pull-up yerine harici, hat kapasitansına göre seçilmiş bir direnç kullanılır; dahili pull-up değeri buna uygun olmayabilir.
AF register ile hangi iş için kullanılacağının belirlenmesi:
- Bu register ile bu peripheral'in hangi başka peripheral için kullanılacağı belirlenir. Bunu yaparken datasheet'e bakarak AF tablosundan (Alternate Function Mapping) hangi pinin hangi AF'de hangi peripheral'e denk geldiğine bakılır. Mesela PB8 pininde AF4 değeri seçildiğinde I2C1_SCL seçilmiş olunur. Yine bu tabloya bakılarak I2C ve SDA/SCL pin seçimine göre belirlemeler yapılır.
- AFR register'ı AFR[0] ve AFR[1] şeklinde kullanılır çünkü 16 pin vardır, bu pinlerin her biri AF seçimi için 4 bit kullanır. Bu durumda 64 bitlik bir register gerekir ama STM32 32 bit register kullandığı için AFR register'ı bu şekilde ikiye bölünmüştür.
I2C register'ının ayarlanması
CR1: İlk Control register'dır. Start, stop, ACK gibi işlemleri yaptırır.
- PE: Peripheral Enable, yani 1 ise I2C açık, 0 ise kapalı.
- START: Bu bit ile I2C start işlemini yapar ve SB flag'ini set eder. Yani SDA'yı falan manuel ayarlamakla uğraşmıyoruz.
- STOP: Aynı mantıkla START ile.
- ACK: Bu ayar ile cihazın her bir byte aldıktan sonra ACK gönderip göndermeyeceği belirlenmiş olunur.
CR2: İkinci Control register'dır. Daha çok konfigürasyon için ayarlar vardır.
- FREQ: Bu alan SCL hızını değil, I2C peripheral'inin bağlı olduğu clock hattının (PCLK1) MHz cinsinden değerini bildirir. Donanım, CCR ve TRISE hesaplarını doğru yapabilmek için kendi input clock frekansını bilmek zorundadır.
- ITEVTEN: Event Interrupt açar. Yani SB, ADDR, STOP, BTF flag'leri oluşunca interrupt oluşmasını enable eder veya kapatır.
- ITERREN: Error Interrupt'tır. Yani Bus Error, Arbitration Lost, NACK, Overrun gibi sorunlar oluşursa interrupt gelmesini açar ya da kapatır.
DR: Data register'dır, CR2'den bağımsız ayrı bir register'dır. Veri buraya gelir, buradan gönderilir. Buraya hem ilk iletişimi başlatmak için karşı cihazın adresi yazılır hem de gönderilecek data yazılır. Reference Manual'da veri gönderiminin bu DR register'ına verinin yazılması ile otomatik başlatıldığı, veri alırken ise gelen verinin bu register'a kopyalandığı belirtilmektedir.
SR1: Donanımın CPU ile konuştuğu, status bilgilerinin alındığı yerdir.
- SB: Start başarılı mı.
- ADDR: Karşı taraf adresi ACK etti, artık bu adrese veri gönderilebilir kontrolü. Bu bitin temizlenmesi için önce SR1 okunur sonra SR2 okunur, sonrasında temizlenmiş olur.
- TXE: Transmit Empty, yani DR'nin boş olduğunun kontrolü.
- RXNE: Receive Not Empty, yani karşı taraftan veri gelmiş, DR'de okunmayı bekleyen veri var, dolu.
- BTF: Byte Transfer Finished, yani verinin tam olarak gönderildiğini söyler, TXE'ye göre daha garantidir, veri tam olarak hatta çıkmıştır.
SR2: Daha çok bus'ın durumunu söyler.
- BUSY: Hatta başka biri konuşuyor.
- MSL: Master mı slave mi onu söyler.
- TRA: Transmit mi receive mi onu söyler.
CCR: Bu register ile SCL hattının clock darbelerinin hızı belirlenir. Ana timer clock'u çok hızlı olduğu için belli bir oranda düşürülmelidir. Oluşacak yeni clock sinyalinin hızı bu register ile belirlenir.
- Formülü şu şekildedir: CCR = timer_hızı / (2 x hedef_SCL_frekansı)
- Burada dikkat edilmesi gereken bu hattın hızının seçimidir çünkü kafamıza göre en yüksek hız için hesaplama yaparak değer veremeyiz. Bu değer belirlenirken hattaki diğer cihazların desteklediği max hız bilinmelidir. Ayrıca fiziksel olarak hattın kapasitansı ve uzunluğu da göz önünde bulundurulmalıdır.
- Hızlar şu şekildedir:
- Standard Mode → 100 kHz → En yaygın, güvenli, en yavaş
- Fast Mode → 400 kHz → Çoğu modern sensör destekler
- Fast Mode Plus → 1 MHz → Yeni nesil cihazlar için
- High Speed Mode → 3.4 MHz → Nadir, özel cihazlar için
TRISE: I2C hattı idle modda 3.3V dadır. Fakat bu hat ilk başlangıçta hemen 3.3V olarak tık diye çıkmaz, 0V dan 1V, 1.5V, 2.5V, 3.3V şeklinde yavaşça yükselir (pull-up üzerinden RC şarjı gibi).
- Bu yüzden hız moduna uygun maksimum bir yükselme (rise time) süresi hesaplanıp bu register'a yazılır. Bu register her transfer sırasında aktif bir bekleme yaptırmaz; başlangıçta bir kere hesaplanıp yazılan ve donanımın SCL/SDA zamanlamasını buna göre ayarlamasını sağlayan bir sınır değeridir.
Bu yazıda I2C register'larının hepsinden bahsetmedim, bu bahsetmediklerim interrupt kullanımı , DMA kullanımı , buffer yapısı ile kullanımı ve SMBus konularından bahsetmedim. Bu konuları ileride ayrı şekilde anlatmayı düşünüyorum. Şimdilik en temeli ile bu şekilde, interrupt 'lar , DMA ler de olmadan init , read, write gibi fonksiyonlar da yine referance manuel kullanılarak while() ile basit şekilde denemek ve görmek için yazılabilir ama gerçek projeler için bu tür kodlar uygun değildir. Eğer hem basit hemde doğru şekilde çalışması gerekiyorsa STM32CubeMX üzerinden proje oluşturulurken I2C peripheral'inin açılması ve ayarlarının en başından yapılması en doğrusu olacaktır. Bu sayede önceden yazılmış HAL kütüphanesinin fonksiyonları ile aynı kullanım daha doğru şekilde yapılabilir. Fakat eğer kod üzerinde ince ayarlamalar yapılması gereken yerler olacaksa yine bu yazıdaki gibi register seviyesinde kodlar yazılması gerekebilir.
İyi Günler.
