"Enter"a basıp içeriğe geçin

STM32F103 (ARM-Cortex M3) ile I2C Master Haberleşme

Merhaba sevgili ziyaretçiler. Bu yazımda STM32F103 mikrodenetleyicisi ile master modda I2C haberleşmeyi irdeliyor olacağız. I2C ifadesi Inter-Integrated Circuit İngilizce ifadesinin açılımıdır. Türkçe anlamı ile entegre devreler arasında anlamına gelir. Diğer bir ismi ise TWI yani Two-Wire Interface yani İki Hatlı Arayüzdür. I2C yada TWI iletişim için yalnızca iki hat kullanan asenkron seri haberleşme protokolüdür.

Bu hatlardan birincisi saat sinyali için kullanılan SCL ucu ve diğeri veri iletimi için kullanılan SDA ucudur. I2C protokolü kullanılarak ısı sensörü, EEPROM, RTC, vs. gibi çevre birimleri yalnızca iki hat kullanarak (bir de GND var) mikrodenetleyiciye kolaylıkla bağlanabilmektedir. I2C protokolü 112 çevre cihazına kadar izin vermektedir. Bu çevre cihazlarına erişim için 7 bitlik adres bilgisi kullanılır. Teoride 7 bitlik adresleme ile 128 cihaz adreslenebilir ancak bazı adresler özel amaçlar için ayrıldığından bu sayı 112’ye düşmektedir. Ayrıca I2C’yi 10 bitlik adres ile kullanılabileceğimiz özel bir metot da mevcuttur.

I2C veri iletişim hattı
I2C veri iletişim hattı (kaynak: http://embeddedsystemengineering.blogspot.com)

I2C iletim hattı üzerinde birden fazla master ve birden fazla slave cihaz olabilir ancak iletim hattına aynı anda yalnızca bir master cihaz veri gönderebilir. Master cihaz bir slave cihazla haberleşebilmek için ona saat sinyali göndermekle mükelleftir. Yani saat sinyali daima master cihaz tarafından üretilir. I2C her zaman açık drenaj sürülür yani işaret her zaman LOW konumunda bulunur, asla HIGH konumunda bulunamaz. Her bir işaret hattında (yani SCL ve SDA) herhangi bir cihaz hattı LOW konumuna çekmediği zaman hattı HIGH konumunda tutmak için bir pull-up direncinin bulunması gerekir.

I2C protokolünün çalışma prensibi
I2C protokolünün çalışma prensibi

Yukarıdaki görsel I2C’nin nasıl çalıştığını göstermektedir. Buna göre START ve STOP durumları, SCL hattı HIGH konumunda tutulurken, SDA hattında yükselen ve düşen kenarlar olarak tanımlanmıştır. Master cihaz bir START sinyali gönderdikten hemen sonra veriyi göndereceği slave cihazı tanımlayan 7 bitlik bir slave adresi ve akabinde bu slave cihazdan okuma mı yapacak yoksa bu cihaza veri mi yazacak onu belirten bir R/W sinyali gönderir. Ardından slave cihaz bu sinyalleşmeye göre bir ACK (kabul edildi) sinyali gönderir. ACK sinyali SDA hattı üzerinde mantıksal LOW sinyali olarak tanımlanmıştır.

Ardından eğer master, slave cihaza veri yazmak isterse (R/W = 0 biti ile gösterilir) bu veriyi byte olarak slave cihaza gönderir. Veri slave cihaza MSB (verinin en yüksek değerlikli biti) tarafından başlanarak gönderilir ve slave her bir baytlık veriyi aldığında, bu veriyi aldığını gösteren ACK sinyalini master cihaza göndermekle sorumludur.

Eğer master cihaz slave cihazdan veri okumak isterse (R/W = 1 biti ile gösterilir), adres ve R/W bitini gönderdikten sonra slave cihazdan aldığı ilk ACK sinyalinden sonra veriyi bayt olarak okuyacaktır. Veri cihazdan MSB (verinin en yüksek değerlikli biti) tarafından başlanarak okunacaktır. Eğer alınan verinin boyutu yalnızca bir bayt ya da alınan bu veri son veri baytı ise master cihaz slave cihaza NACK sinyali gönderir, aksi durumda ise ACK sinyali gönderir. NACK sinyali SDA hattı üzerinde mantıksal HIGH sinyali olarak tanımlanmıştır.

Aşağıdaki görselde I2C veri hattının ayrıntılı zamanlama çizelgesini görebilirsiniz.

I2C veri hattı işaret zamanlama çizelgesi
I2C veri hattı işaret zamanlama çizelgesi

Bu yazıda I2C master cihaz olarak STM32F103 mikrodenetleyicisini, slave cihaz olarak da bir Arduino cihazını kullandık. Slave cihazın yaptığı iş, eğer master cihaz, slave cihaza 0x01 verisini yazarsa Arduino kartı üzerindeki LED 250 ms lik aralıklarla blink yapacak. LED’i söndürmek içinse master cihaz slave cihaza (Arduino) 0x00 verisini göndermelidir. Ayrıca master, LED’in on/off durumunu slave cihazdan 1 veya 0 olarak okuyabilir.

Aşağıdaki kod parçası ile Arduino kartımızı I2C slave olarak ayarlayacağız.

#include <Wire.h>

#define OWN_ADDRESS  0x08
#define LED_PIN      13

int ledBlink = 0;

void receiveEvent(int bytes) 
{
    // I2C hattındaki veriyi oku
    ledBlink = Wire.read();
}

void requestEvent()
{
    // LED'in durumunu master cihaza gönder
    Wire.write(ledBlink);
}

void setup() 
{
    pinMode (LED_PIN, OUTPUT);
  
    // I2C hattını slave olarak başlat
    Wire.begin(OWN_ADDRESS); 
    // I2C veri hattından veri alındığında tetiklenecek metot
    Wire.onReceive(receiveEvent);
    // I2C veri hattından veri istendiğinde tetiklenecek metot
    Wire.onRequest(requestEvent);
}

void loop() 
{
    // Eğer alınan veri 1 ise 250 ms aralıklarla LED blink yapsın
    if (ledBlink == 1) 
    {
        digitalWrite(LED_PIN, HIGH);
        delay(250);
        digitalWrite(LED_PIN, LOW);
        delay(250);
    }
    // Eğer alınan veri 0 ise LED'i söndür
    else if (ledBlink == 0) 
    {
        digitalWrite(LED_PIN, LOW);
    }
}

Aşağıdaki kod parçası ile STM32F103 çipini I2C master olarak ayarlayacağız. i2c_init() fonksiyonunu I2C çevre aygıtını başlatmak için kullanıyoruz. Ana döngü içinde Arduino bordu üzerindeki LED’i her 2500 milisaniyede bir blink yaptıracak bir komut yazdık. Her yazma komutundan sonra bir de LED’in anlık durumunu okuyan bir komut olacak ve LED’in durumunu 16×2 LCD ekran üzerinde gösterecek.

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "delay.h"
#include "lcd16x2.h"
 
#define I2Cx_RCC        RCC_APB1Periph_I2C2
#define I2Cx            I2C2
#define I2C_GPIO_RCC    RCC_APB2Periph_GPIOB
#define I2C_GPIO        GPIOB
#define I2C_PIN_SDA     GPIO_Pin_11
#define I2C_PIN_SCL     GPIO_Pin_10
 
#define SLAVE_ADDRESS    0x08
 
void i2c_init(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_address_direction(uint8_t address, uint8_t direction);
void i2c_transmit(uint8_t byte);
uint8_t i2c_receive_ack(void);
uint8_t i2c_receive_nack(void);
void i2c_write(uint8_t address, uint8_t data);
void i2c_read(uint8_t address, uint8_t* data);
 
uint8_t receivedByte;

int main(void)
{
    DelayInit();
    lcd16x2_init(LCD16X2_DISPLAY_ON_CURSOR_OFF_BLINK_OFF);
 
    // I2C donanımını başlat
    i2c_init();
 
    while (1)
    {
        // Arduino borda (slave) 0x01 yaz (LED blink'i başlat)
        i2c_write(SLAVE_ADDRESS, 0x01);
        DelayMs(5);
        // LED blinkin durumunu oku (açık mı, kapalı mı)
        i2c_read(SLAVE_ADDRESS, &receivedByte);
        // LED blinkin durumunu oku
        lcd16x2_clrscr();
        if (receivedByte == 0)
        {
            lcd16x2_puts("LED Blink Kapali");
        }
        else if (receivedByte == 1)
        {
            lcd16x2_puts("LED Blinking Acik");
        }
        DelayMs(2500);
  
        // Arduino bord'a (slave) 0x00 yaz (LED blinki kapat)
        i2c_write(SLAVE_ADDRESS, 0x00);
        DelayMs(5);
        // LED blink'in durumunu oku (açık mı, kapalı mı)
        i2c_read(SLAVE_ADDRESS, &receivedByte);
        // LED blink'in durumunu LCD'de göster
        lcd16x2_clrscr();
        if (receivedByte == 0)
        {
            lcd16x2_puts("LED Blink Kapali");
        }
        else if (receivedByte == 1)
        {
            lcd16x2_puts("LED Blink Acik");
        }
        DelayMs(2500);
    }
}

void i2c_init()
{
    // MCU'nun başlangıç işlemlerini yapan yapı tanımları 
    I2C_InitTypeDef I2C_InitStruct;
    GPIO_InitTypeDef GPIO_InitStruct;

    // 1. Adım: I2C'yi başlat
    RCC_APB1PeriphClockCmd(I2Cx_RCC, ENABLE);
    I2C_InitStruct.I2C_ClockSpeed = 100000;
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStruct.I2C_OwnAddress1 = 0x00;
    I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2Cx, &I2C_InitStruct);
    I2C_Cmd(I2Cx, ENABLE);
 
    // 2. Adım: GPIO (GPIOB Pin 10-11) hattını açık drenaj I2C alternatif fonksiyonu ile başlat
    RCC_APB2PeriphClockCmd(I2C_GPIO_RCC, ENABLE);
    GPIO_InitStruct.GPIO_Pin = I2C_PIN_SCL | I2C_PIN_SDA;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(I2C_GPIO, &GPIO_InitStruct);
}

void i2c_start()
{
    // I2Cx müsait olana kadar bekle
    while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));

    // Başlat durumunu oluştur
    I2C_GenerateSTART(I2Cx, ENABLE);

    // I2C EV5 durumu için bekle.
    // Bunun anlamı başlangıç durumu düzgün bir şekilde I2C hattına iletilmiş demektir
    // (veri hattı boştur ve başka cihazlar iletişimde değildir) 
    while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
}

void i2c_stop()
{
    // I2C durdurma koşulunu oluşturur
    I2C_GenerateSTOP(I2Cx, ENABLE);
    // Durdurma koşulu tamamlanana kadar bekle
    while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_STOPF));
}

void i2c_address_direction(uint8_t address, uint8_t direction)
{
    // Slave adresini gönder
    I2C_Send7bitAddress(I2Cx, address, direction);

    // I2C EV6 durumu için bekler
    // Bunun anlamı slave cihaz adresini doğru bir şekilde almış demektir.
    if (direction == I2C_Direction_Transmitter)
    {
        while (!I2C_CheckEvent(I2Cx,
            I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    }
    else if (direction == I2C_Direction_Receiver)
    { 
        while (!I2C_CheckEvent(I2Cx,
            I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    }
}

void i2c_transmit(uint8_t byte)
{
    // Veriyi gönder
    I2C_SendData(I2Cx, byte);
    // I2C EV8_2 durumunu bekle.
    // Bunun anlamı veri fiziksel olarak veri hattı üzerinde iletilmektedir.
    while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}

uint8_t i2c_receive_ack()
{
    // Alınan veri için alındı (ACK) bilgisini aktifleştir
    I2C_AcknowledgeConfig(I2Cx, ENABLE);
    // I2C EV7 durumunu bekle
    // Bunun anlamı veri I2C veri yazmacına alınmıştır.
    while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));

    // Veriyi I2C veri yazmacından oku ve fonksiyondan döndür
    return I2C_ReceiveData(I2Cx);
}

uint8_t i2c_receive_nack()
{
    // Alınan verinin alındı (ACK) bilgisini pasif yap
    I2C_AcknowledgeConfig(I2Cx, DISABLE);
    // I2C EV7 durumu için bekle
    // Bunun anlamı veri I2C veri yazmacına alınmıştır.
    while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));

    // Veriyi I2C veri yazmacından oku ve geri döndür
    return I2C_ReceiveData(I2Cx);
}

void i2c_write(uint8_t address, uint8_t data)
{
    i2c_start();
    i2c_address_direction(address << 1, I2C_Direction_Transmitter);
    i2c_transmit(data);
    i2c_stop();
}

void i2c_read(uint8_t address, uint8_t* data)
{
    i2c_start();
    i2c_address_direction(address << 1, I2C_Direction_Receiver);
    *data = i2c_receive_nack();
    i2c_stop();
}

Haruncetin.com.tr

İlk Yorumu Siz Yapın

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir