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 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.
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.
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