7 – I2C Modülü

Bu yazımızda MSP430’un USI Modülü içerisindeki I2C donanımını inceleyeceğiz. USI modülü içerisinde bulunan SPI donanımını daha önce incelemiştik. I2C donanımında ise bazı birimler ortak kullanıldığı için SPI Modülü başlıklı yazımda anlattığım USI biriminin temel özelliklerini iyi kavrayanlar için I2C donanımını çalıştırmak ve anlamak çok zor olmayacaktır. Bu sebeple bu yazıyı okumadan önce USI donanımını hatırlamak adına SPI Modülü başlıklı yazımı tekrar okumanızı tavsiye ediyorum.

 

I2C donanımına bakıldığında çok da kompleks olmayan bir yapıyı aşağıdaki diyagramdan görebilirsiniz.

I2C Modülü Blok Diyagramı

Temel olarak SPI modülü ile birçok ortak nokta bulunuyor. Frekans bölücü, Kesmeler, Bit Counter ve Shift Register bu ortak birimlere örnek olarak gösterilebilir. İki birim arasındaki tek fark ise SPI Modülünde 16 bit haberleşme mümkünken, I2C Modülünde maksimum 8 bit haberleşmeye izin veriliyor. Onun haricinde yukarıda sayılan diğer temel donanımların kullanımı aynı.

Yukarıdaki blok diyagramda START Detect ve STOP Detect olmak üzere iki ayrı birim daha bulunmakta. Bunun açıklamasına ise yazının ilerileyen kısımlarında değineceğiz.

Tarihçe

I²C (Inter-Integrated Circuit),  Philips firması tarafından geliştirilmiş bir sistemdir. Ve bu yapıyı anakartlarda, gömülü sistemlerde ve cep telefonlarında kullanmıştır. 1990’ların ortasından bu yana Siemens, NEC, Motorola gibi birçok farklı firma I2C standardı ile tam uyumlu ürünler piyasaya sürmüşlerdir.

 

I2C Modu/Donanımı

USI modülünde I2C donanımını çalıştırabilmek için USII2C = 1, USICKPL = 1, ve USICKPH = 0 yapılmalıdır. Bu birim kullanıldığında USI Shift Registeri 8 bit olarak kullanılacağından dolayı USI16B biti 0 olmalıdır. Bu sebeple Shift Registera erişim USILSB üzerinden olmaktadır.

Data ve Clock çıkışlarını pinlerden alabilmek için ise USIPE6 (SCL) ve USIPE7(SDA) bitleri set edilmelidir.

 

I2C donanımında clock(SCL) sadece Master tarafından üretilir ve Slave cihazlara master cihazın ürettiği bu clock hattı paralel olarak bağlanır. Normalde donanım sadece clock sinyali üretmekte ve kaydırmalı gönderme yapmaktadır. Bir master cihazın birden çok slave cihazla haberleşmesi ve ACK / NACK durumları için yazılımsal çözümler uygulanmaktadır.

Bazı gelişmiş I2C donanımları bu yazılımsal kısımları kendi donanımı içerisinde barındırarak daha efektif çalışabilmektedirler. Ayrıca bu şekilde yazılımcı üzerindeki yük de bir nebze hafiflemektedir. Yazılımsal olarak yapılması gereken en önemli kısım ise: her slave cihaz için bir adres belirlemek ve veri gönderilmeden önce aynı verir gönderir gibi belirlenen slave adresi göndermektir. Bu şekilde slave cihazlar hattaki bu veriyi dinleyip, eğer adres olarak gönderilen bu veri kendi adresleri ile uyuşuyorsa karşı tarafa onay(ACK) biti göndererek haberleşmenin başlamasını sağlarlar.

 

I2C hattına bağlı olan slave cihazlar için tanımlı olan bu adres uzunluğu maksimum 7 bittir ve bir hatta aynı anda maksimum 127 cihaz bağlanabilir.

7.1. I2C Master Modu

I2C birimini Master modunda kullanabilmek için USIMST = 1 yapılmalıdır. USIIFG = 0 olduğu sürece üretilen clock palsleri SCL pinine aktarılmaktadır. Eğer USIIFG = 1 ise yani kesme bayrağı temizlenmemişse clock plasleri SCL pinine aktarılamayacağından dolayı haberleşme başarısız olacaktır.

 

7.2. I2C Slave Modu

I2C birimini Slave modunda kullanabilmek için ise USIMST = 0 yapılmalıdır. Eğer USIIFG = 1 veya USISTTIFG = 1 veya USICNT = 0 ise Master SCL üretse de, Slave kısmında herhangi bir alım veya kesmeye gitme işlemi yapılmayacaktır.

 

7.3. I2C Transmitter

Bu kısım USI modülünde herhangi bir birim/donanım değildir. Haberleşme protokolünün bir parçasıdır. Temel olarak karşı tarafa SDA hattından veri aktarmak için yapılması gereken adımlar bu kısımda anlatılmaktadır.

  • USISRL saklayıcısına gönderilmek istenen 8 bitlik veri yüklenir.
  • USIOE aktif edilir.
  • USICNT saklayıcısına 8 değerini yüklediğimiz zaman USISRL saklayıcısına yüklenen veri yani karşı tarafa gönderilmek istenen veri karşı tarafa aktarılacaktır.
  • Veri tamamen gönderildiğinde USIIFG = 1 olur ve SCL pinindeki pals üretimi durur.
  • Karşı taraftan 1 bitlik onay(ACK) ya da Ret(NACK) bildirimini alabilmek için USIOE = 0 ve USICNT = 1 yapılmalıdır.
  • USICNT = 1 yapıldığı zaman otomatik olarak USIIFG = 0 olmaktadır.
  • USIIFG tekrar 1 olduğunda karşı taraftan onay biti gelmiş demektir. USISRL saklayıcısının ilk biti kontrol edilerek haberleşme devam ettirilir veya sonlandırılır.

 

7.4. I2C Reveiver

I2C Transmitter başlığında bahsedildiği üzere I2C Receiver kısmı da haberleşme protokolünün bir parçasıdır. Yani yazılımda izlenmesi gereken adımları anlatmaktadır.

  • USIOE = 0 yapılarak I2C veri hattından(SDA) gelen verinin alınması sağlanır.
  • Master kısmından 8 bitlik veri geleceği için Slave tarafında da USICNT = 8 yapılarak gelen verinin 8 bit olarak alınması sağlanır.
  • USINCNT = 8 yapıldığı zaman otomatik olarak USIIFG temizlenmektedir(USI Interrupt Flag).
  • 8 bitlik veri alındığı zaman USICNT = 0 olmakta ve USIIFG = 1 olmaktadır. Bu adımda 8 bitlik veri alınmış demektir.
  • Alınan veri USISRL saklayıcısından okunabilir.
  • Karşı tarafa 1 bitlik onay(ACK) ya da ret(NACK) bildirimi gönderebilmek için USIOE = 1 yapılmalıdır.
  • ACK için USISRL = 0, NACK için USISRL = 0xFF yapılmalıdır.
  • 1 bitlik veriyi karşı tarafa gönderebilmek için USICNT = 1 yapılmalıdır.
  • Onay biti gönderildiğinde USIIFG tekrar 1 olmaktadır. Bu durumdan sonra Slave cihazın tekrar veri alabilmesi için donanım tekrar veri alacak şekilde ayarlanmalıdır.(İlk madde)

 

Master ve Slave Cihazların I2C Hattına Bağlanması

Yukarıdaki resimde görüldüğü gibi Slalve yani ikincil cihazlar SDA ve SCL hattına paralel olarak bağlanmışlardır. Bazı mikrodenetleyicilerde pinler içeriden pull-up veya pull-down yapılabilmektedirler. Bu şekilde herhanbi bir direnç kullanmaksızın master ve slave arası haberleşme doğrudan kablo bağlanarak sağlanabilir. Eğer kullanılan mikrodenetleyici içerisinde dahili pull-up dirençleri bulunmuyorsa SDA ve SCL hatlarını 10k lık birer adet dirençle +5v / +3v3 a çekmek zorunludur. (MCU ya ve pin töleranslarına göre değişiklik gösterebilir)

Bizim örneğimizde dahili pull-up dirençleri kullanılmıştır.

7.5. START Durumu

I2C donanımındaki START Detect birimi, USISRL saklayıcınsa 0 yüklendiği zaman otomatik olarak START Condition oluşumunu sağlamaktadır. Bunun için aşağıdaki adımlar uygulanabilir.

 

  • USISRL = 0 yapılmalıdır.
  • USICTL0 saklayıcısından USIGE = 1 ve USIOE = 1 yapılmalıdır.
  • USICTL0 saklayıcısından USIGE = 0 yapılarak kilit(latch) devre dışı bırakılmalıdır.

 

7.6. STOP Durumu

  •  USISRL = 0xFF yapılmalıdır.
  • USICTL0 saklayıcısından USIGE = 1 yapılmalıdır.
  • USICTL0 saklayıcısından USIGE = 0 ve USIOE = 0 yapılarak kilit ve çıkış devre dışı bırakılmalıdır.

 

7.7. Kesmeler

USI Modülü için yanlızca bir adet kesme vektörü bulunmaktadır. I2C birimi için atanmış 2 farklı kesme bayrağı(USIIFG ve USISTTIFG) bu vektör içerisinde kontrol edilebilir. Her kesme bayrağının kendine ait aktif etme(enable) biti bulunmaktadır. Kesmeler aktif edilir ve GIE = 1 yapılırsa kesme anında program o kesmeye ait vektöre dallanacaktır.

USICNT = 0 olduğunda USIIFG bayrağı set edilir. Eğer USIIFGCC = 0 ise USICNT saklayıcısına 0 dan büyük bir değer yazılması anında USIIFG bayrağı da temizlenir.

Bir START Durumu tespit edildiğinde ise USISTTIFG = 1 olur. Bu bayrak yazılımla temizlenmelidir.

Bir STOP Durumu tespit edildiğinde ise USISTP = 1 olur. Bu bayrak için atanmış herhangi bir kesme fonksiyonu/özelliği yoktur. Eğer USIIFGCC = 0 ise USICNT saklayıcısına 0 dan büyük bir değer yazmak USISTP bayrağını 0 yapacaktır. Veya doğrudan yazılımla 0 yapılabilir.

 

7.8. Saklayıcılar

Şema

NOT : SDA ve SCL bağlantılarında dahili pull-up dirençleri kullanılmıştır.

Örnek Devre İçin Bağlantı Şeması

Kod

 

Uygulamanın kod kısmında Texas Instruments’in örnek kodlarından yararlanılmıştır. Temel mantığı ise bir dizi içeriğinden her seferinde 1 byte veri gönderilerek karşı tarafta alınan bu verinin doğrudan LCD ekranda gösterilmesi işlemi yaptırılmıştır. Dizinin sonuna gelindiğinde ise Master kısmındaki program sonlandığı için haberleşme durmaktadır.

Haberleşme hızı; SMCLK / 8 => 1MHz / 8 = ~125 kHz dir.

#include  "io430.h"
#include  "in430.h"
 
char* dizi = "Fatih INANC";
int  I2C_State;
 
void main(void)
{
 
  volatile unsigned int i;             
 
  WDTCTL = WDTPW + WDTHOLD;
  if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)
  {
    while(1);                          
 
  }
  BCSCTL1 = CALBC1_1MHZ;
  DCOCTL = CALDCO_1MHZ;
 
  P1OUT = 0xC0;
  P1REN |= 0xC0;
  P1DIR = 0xFF;
  P2OUT = 0;
  P2DIR = 0xFF;
 
  USICTL0 = USIPE6+USIPE7+USIMST+USISWRST;
  USICTL1 = USII2C+USIIE;
  USICKCTL = USIDIV_3+USISSEL_2+USICKPL;
  USICNT |= USIIFGCC;
  USICTL0 &= ~USISWRST;
  USICTL1 &= ~USIIFG;
  _EINT();
 
  while(*dizi!='\0')                   // Dizinin sonuna gelene kadar tekrarla...
  {
    USICTL1 |= USIIFG;
    LPM0;
    _NOP();
    for (i = 0; i < 10000; i++);
  }
}
 
/******************************************************
// USI interrupt service routine
******************************************************/
 
#pragma vector = USI_VECTOR
__interrupt void USI_TXRX (void)
{
  switch(I2C_State)
    {
      case 0: // Generate Start Condition & send address to slave
	      P1OUT |= 0x01;
              USISRL = 0x00;
              USICTL0 |= USIGE+USIOE;
              USICTL0 &= ~USIGE;
              USISRL = SLV_Addr;
              USICNT = (USICNT & 0xE0) + 0x08;
              I2C_State = 2;
              break;
 
      case 2: // Receive Address Ack/Nack bit
              USICTL0 &= ~USIOE;
              USICNT |= 0x01;
              I2C_State = 4;
              break;
 
      case 4: // Process Address Ack/Nack & handle data TX
              USICTL0 |= USIOE;
              if (USISRL & 0x01)
              { // Send stop...
                USISRL = 0x00;
                USICNT |=  0x01;
                I2C_State = 10;
                P1OUT |= 0x01;
              }
              else
              { // Ack received, TX data to slave...
                USISRL = *dizi;
                USICNT |=  0x08;
                I2C_State = 6;
                P1OUT &= ~0x01;
              }
              break;
 
      case 6: // Receive Data Ack/Nack bit
              USICTL0 &= ~USIOE;
              USICNT |= 0x01;
              I2C_State = 8;
              break;
 
      case 8: // Process Data Ack/Nack & send Stop
              USICTL0 |= USIOE;
              if (USISRL & 0x01)
                P1OUT |= 0x01;
              else
              {
                dizi++;
                P1OUT &= ~0x01;
              }
              // Send stop...
              USISRL = 0x00;
              USICNT |=  0x01;
              I2C_State = 10;
              break;
 
      case 10:// Generate Stop Condition
              USISRL = 0x0FF;
              USICTL0 |= USIGE;
              USICTL0 &= ~(USIGE+USIOE);
              I2C_State = 0;
              LPM0_EXIT;
              break;
    }
 
  USICTL1 &= ~USIIFG;                  // Clear pending flag
}

 

#include  "io430.h"
#include  "in430.h"
#include  "lcd_595.h"
 
char SLV_Addr = 0x90;
int I2C_State = 0;                    
 
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;
  if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)
  {
    while(1);                          
 
  }
  BCSCTL1 = CALBC1_1MHZ;
  DCOCTL = CALDCO_1MHZ;
 
  P1OUT = BIT7 + BIT6;
  P1REN |= 0xC0;
  P1DIR = 0xFF;
  P2OUT = 0;
  P2DIR = 0xFF;
 
  USICTL0 = USIPE6+USIPE7+USISWRST;
  USICTL1 = USII2C+USIIE+USISTTIE;
  USICKCTL = USICKPL;
  USICNT |= USIIFGCC;
  USICTL0 &= ~USISWRST;
  USICTL1 &= ~USIIFG;
  _EINT();
 
  lcd_init();
  lcd_puts("MSP430 - USI/I2C");
  lcd_goto(2,1);
  while(1)
  {
    LPM0;
    _NOP();
  }
}
 
//******************************************************************************
// USI interrupt service routine
//******************************************************************************
#pragma vector = USI_VECTOR
__interrupt void USI_TXRX (void)
{
  if (USICTL1 & USISTTIFG)
  {
    P1OUT |= 0x01;
    I2C_State = 2;
  }
 
  switch(I2C_State)
    {
      case 0: // Idle, should not get here
              break;
 
      case 2: // RX Address
              USICNT = (USICNT & 0xE0) + 0x08;
              USICTL1 &= ~USISTTIFG;
              I2C_State = 4;
              break;
 
      case 4: // Process Address and send (N)Ack
              if (USISRL & 0x01)
                SLV_Addr++;
              USICTL0 |= USIOE;
              if (USISRL == SLV_Addr)
              {
                USISRL = 0x00;
                P1OUT &= ~0x01;
                I2C_State = 8;
              }
              else
              {
                USISRL = 0xFF;
                P1OUT |= 0x01;
                I2C_State = 6;
              }
              USICNT |= 0x01;
              break;
 
      case 6: // Prep for Start condition
              USICTL0 &= ~USIOE;
              SLV_Addr = 0x90;
              I2C_State = 0;
              break;
 
      case 8: // Receive data byte
              USICTL0 &= ~USIOE;
              USICNT |=  0x08;
              I2C_State = 10;
              break;
 
      case 10:// Check Data & TX (N)Ack
              USICTL0 |= USIOE;
              lcd_putch(USISRL);
              USISRL = 0x00;
              P1OUT &= ~0x01;
              USICNT |= 0x01;
              I2C_State = 6;
              break;
    }
 
  USICTL1 &= ~USIIFG;                  // Clear pending flags
}

Resimler

 

 


Video

 

7 – I2C Modülü” üzerine 9 düşünce

  1. ismail HANCI

    Örnek Devre İçin Bağlantı Şeması için proteus ekran görüntüsü var sanırım. Proteus ortamında msp430g2231’i nasıl çalıştırabiliriz?

    Cevapla
    1. M.Fatih İNANÇ Yazar

      Merhaba İsmail,

      Örnek devre şeması Proteus programından sadece bağlantı şeklini göstermek için hazırlandı. Oradaki G2231’i ben temsili olarak Proteus’ta oluşturmuştum.
      Ekran görüntüsü ise videodaki el osilaskopundan alınma ekran görüntüsüdür.

      Cevapla
  2. Alaattin KUL

    Takıldığım bir nokta var.Basit birşey ama anlayamadım.
    Bu “USICNT |= USIIFGCC; USICTL0 &= ~USISWRST;” gibi işlemlerde ” |= &= ~” operatörleri ne amaçla kullanılıyor? Bildiğim kadarıyla AND ve OR mantık işlemlerini yerine getiriyorlar ama burda ki kullanımına bir anlam veremedim. Mesela USICTL0 |= USIGE+USIOE; bu satırların anlamı nedir?
    USICTL0 &= ~USIGE;”

    Cevapla
    1. M.Fatih İNANÇ Yazar

      Merhaba Alaattin,

      Bahsettiğiniz operatörler maskeleme amaçlı kullanılıyorlar. Maskeleme bir saklayıcıda sadece istenilen biti değiştirmek anlamına gelmektedir.
      Eğer istediğimiz biti 0 yapacaksak saklayıcının eski halini yenisi ile AND işlemine tabi tutarız ve istediğimiz biti “~bit” ile 0 yaparız. Burada MSP430 un structure larında saklayıcıların yanına o bitin ismini yazmak her zaman 1 kabul edilir. Dolayısiyle de “~bit” gibi bir ifade her zaman o bitin 0 yapılacağı anlamına gelir.

      Örn : P1IFG saklayıcısının 3 üncü biti 1 olsun. Yani bir P1.3 kesmesi gelmiş olsun.
      Bu durumda bu saklayıcının değeri = 0000 1000 olacaktır. İşleme gelecek olursak ;

      P1IFG = 0x08;
      BIT3 = 0x08; //0 yapacağımız bit

      İşlem yapcak olursak;
      P1IFG &= ~BIT3; => P1IFG = (P1IFG & (~BIT3)); //İşlem

      P1IFG = (0x08 & (~0x08))
      P1IFG = (0x08 & (0xF7))
      P1IFG = 0 olacaktır.

      Eğer herhangi bir biti 1 yapmak istersek bunun için ise doğrudan o eski veriyi 1 leyeceğimiz bit ile OR işlemine tabi tutabiliriz.

      P1OUT = 0x01; //P1OUT un eski değeri.
      P1OUT |= 0x40;

      P1OUT = 0x41; // P1OUT yeni değeri.

      Bu örnek de LaunchPad üzerindeki ledlerden önce kırmızı olanı sonra ise yeşil olanı yakacaktır.

      Kolay Gelsin.

      Cevapla
  3. Alaattin KUL

    Teşekkür ederim anlatımınız için çok yardımcı oldu,bir ufak sorum daha olacak;

    şimdi burdaki işlemin anlamı –> USICNT = (USICNT & 0xE0) + 0x08;

    USICNT (AND) 1110 0000 => Yani USICNT saklayıcısına bakarsak
    baştaki 3bit aynı kalsın (1 ile çarpıyoruz) ve sayıcı kısmı (son 5bit) sıfırlansın.
    Ve 0x08 ile toplayarak da sayıcıya 8 yüklüyoruz.

    Peki şimdi doğru anladıysam; ((master programının “case 0” bloğunda ki))

    USICNT = (USICNT & 0xE0) + 0x08; yerine USICNT |= 0x08; neden yapmadık?

    USICNT’nin counter bitlerine ben bişe yüklemediğime göre başlangıçta sıfırdır heralde,

    yoksa olmama ihtimaline karşı garanti olsun diye mi yaptık?

    Cevapla
  4. Kursad

    Merhaba Fatih Bey;

    ” master_main.c ” dosyasını derlerken ” SLV_Addr ” tanımlanmamış gibisinden uyarı verdi bu uyarıyı ” slave_main.c ” de bulunan ” char SLV_Addr = 0x90; ” tanımlamasını ” master_main.c ” ye ekleyerek çözdük fakat uygulama çalışmadı slave olan tarafta lcd’de msp430 yazısı yazıyor fakat master olan taraf “Fatih INANC” yazısını basmıyor bunun sebebi ne olabilir yardımınızı bekliyoruz teşekkürler.

    Cevapla
    1. M.Fatih İNANÇ Yazar

      Merhaba,

      Bir sebepten dolayı bağlantı sağlanamıyor gibi görünüyor.
      Eğer LaunchPad üzerinde deneme yapıyorsanız. P1.6 LED jumperını bağlı olmadığından emin olun.

      Cevapla
  5. ahmet

    bu uygulamayı bende gerçekleştirdim bende aynı sorunla karşılaştım sorun fatih hocamın dediği gibi P1.6 LED jumperını sökünce düzeliyor iyi çalışmalar…

    Cevapla
  6. ahmet

    Hocam bu örnekten yararlanarak 24c64 harici eepromu nasıl kullanırız. yaptığınız uygulamada master kısmını fonksiyon haline getirebilirsek bütün i2c haberleşen cihazları kullanabiliriz diye düşünüyorum bu konuda nasıl yol izleriz.

    Cevapla

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir