Picproje Elektronik Sitesi

DERLEYİCİLER => PIC C => Konuyu başlatan: talhaabus - 28 Mayıs 2022, 09:18:53

Başlık: I2C Yardım
Gönderen: talhaabus - 28 Mayıs 2022, 09:18:53
Merhaba PIC16F887A ile I2C haberleşmesi kullanarak sıcaklık nem sensöründen veri almaya çalışıyorum. Fakat I2C ile ilgili kavrayamadığım bazı noktalar var. Her nerede konuyu aratsam görüyorum ki herkes I2C ayarlarını yapan fonksiyonları yazıyor ve bırakıyor. Kimse kodu yazıp sensörden veriyi çektiğini göstermiyor. Bu haberleşme ile veri çekmek için tam olarak ne yazmamız gerekir? Hazırladığımız I2C_write fonksiyonunu kullanarak slave cihaza veri mi gönderiyoruz öncelikle? Daha sonrasında read fonksiyonu ile veri mi topluyoruz?  Eğer gönderiyorsak bu slave cihazın adresini ne olarak yazıyoruz?

Kod ve devre şeması aşağıdaki gibidir


(https://i.ibb.co/319yHzq/1.png) (https://ibb.co/319yHzq)

#include<htc.h>
#include <stdio.h>

__CONFIG( FOSC_HS & WDTE_OFF & PWRTE_ON & CP_OFF & BOREN_ON
          & LVP_OFF & CPD_OFF & WRT_OFF & DEBUG_OFF);


#define _XTAL_FREQ  40000000
// Define Pins for LCD
#define LCD_E          RB0    // Enable pin for LCD
#define LCD_RS        RB1    // RS pin for LCD
#define LCD_Data_Bus_D4    RB4    // Data bus bit 4
#define LCD_Data_Bus_D5    RB5    // Data bus bit 5
#define LCD_Data_Bus_D6    RB6    // Data bus bit 6
#define LCD_Data_Bus_D7    RB7    // Data bus bit 7
// Define Pins direction register
#define LCD_E_Dir          TRISB0
#define LCD_RS_Dir          TRISB1
#define LCD_Data_Bus_Dir_D4    TRISB4
#define LCD_Data_Bus_Dir_D5    TRISB5
#define LCD_Data_Bus_Dir_D6    TRISB6
#define LCD_Data_Bus_Dir_D7  TRISB7
// Constants
#define E_Delay      500
// Function Declarations
void WriteCommandToLCD(unsigned char);
void WriteDataToLCD(char);
void InitLCD(void);
void WriteStringToLCD(const char*);
void ClearLCDScreen(void);
// Define i2c pins
#define SDA      RC4        // Data pin for i2c
#define SCK      RC3        // Clock pin for i2c
#define SDA_DIR    TRISC4      // Data pin direction
#define SCK_DIR    TRISC3      // Clock pin direction
// Define i2c speed
#define I2C_SPEED  100        // kbps
//Function Declarations
void InitI2C(void);
void I2C_Start(void);
void I2C_ReStart(void);
void I2C_Stop(void);
void I2C_Send_ACK(void);
void I2C_Send_NACK(void);
unsigned char  I2C_Write_Byte(unsigned char);
unsigned char I2C_Read_Byte(void);

int main(void)
{

    // Initialize LCD
    InitLCD();
    // Initialize i2c module
    InitI2C();


    while(1)
    {
 
    }
    return 0;
}
//Function related to LCD
void ToggleEpinOfLCD(void)
{
    LCD_E = 1;                // Give a pulse on E pin
    __delay_us(E_Delay);      // so that LCD can latch the
    LCD_E = 0;                // data from data bus
    __delay_us(E_Delay);
}
void WriteCommandToLCD(unsigned char Command)
{
    LCD_RS = 0;          // It is a command
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= (Command&0xF0);  // Write Upper nibble of data
    ToggleEpinOfLCD();      // Give pulse on E pin
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= ((Command<<4)&0xF0); // Write Lower nibble of data
    ToggleEpinOfLCD();      // Give pulse on E pin
}
void WriteDataToLCD(char LCDChar)
{
    LCD_RS = 1;          // It is data
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= (LCDChar&0xF0);  // Write Upper nibble of data
    ToggleEpinOfLCD();      // Give pulse on E pin
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= ((LCDChar<<4)&0xF0); // Write Lower nibble of data
    ToggleEpinOfLCD();      // Give pulse on E pin
}
void InitLCD(void)
{
    // Firstly make all pins output
    LCD_E            = 0;  // E  = 0
    LCD_RS          = 0;  // RS = 0
    LCD_Data_Bus_D4    = 0;    // Data bus = 0
    LCD_Data_Bus_D5    = 0;    // Data bus = 0
    LCD_Data_Bus_D6    = 0;    // Data bus = 0
    LCD_Data_Bus_D7    = 0;    // Data bus = 0
    LCD_E_Dir        = 0;  // Make Output
    LCD_RS_Dir          = 0;  // Make Output
    LCD_Data_Bus_Dir_D4  = 0;  // Make Output
    LCD_Data_Bus_Dir_D5  = 0;  // Make Output
    LCD_Data_Bus_Dir_D6  = 0;  // Make Output
    LCD_Data_Bus_Dir_D7  = 0;  // Make Output
    ///////////////// Reset process from datasheet //////////////
    __delay_ms(40);
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= 0x30;        // Write 0x3 value on data bus
    ToggleEpinOfLCD();      // Give pulse on E pin
    __delay_ms(6);
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= 0x30;        // Write 0x3 value on data bus
    ToggleEpinOfLCD();      // Give pulse on E pin
    __delay_us(300);
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= 0x30;        // Write 0x3 value on data bus
    ToggleEpinOfLCD();      // Give pulse on E pin
    __delay_ms(2);
    PORTB &= 0x0F;        // Make Data pins zero
    PORTB |= 0x20;        // Write 0x2 value on data bus
    ToggleEpinOfLCD();      // Give pulse on E pin
    __delay_ms(2);
    /////////////// Reset Process End ////////////////
    WriteCommandToLCD(0x28);    //function set
    WriteCommandToLCD(0x0c);    //display on,cursor off,blink off
    WriteCommandToLCD(0x01);    //clear display
    WriteCommandToLCD(0x06);    //entry mode, set increment
}
void WriteStringToLCD(const char *s)
{
    while(*s)
    {
        WriteDataToLCD(*s++);  // print first character on LCD
    }
}
void ClearLCDScreen(void)      // Clear the Screen and return cursor to zero position
{
    WriteCommandToLCD(0x01);    // Clear the screen
    __delay_ms(2);              // Delay for cursor to return at zero position
}
//Function related to I2C
// Function Purpose: Configure I2C module
void InitI2C(void)
{
    SDA_DIR = 1;    // Make SDA and
    SCK_DIR = 1;    // SCK pins input
    SSPADD  = ((_XTAL_FREQ/4000)/I2C_SPEED) - 1;
    SSPSTAT = 0x80;    // Slew Rate control is disabled
    SSPCON  = 0x28;    // Select and enable I2C in master mode
}
// Function Purpose: I2C_Start sends start bit sequence
void I2C_Start(void)
{
    SEN = 1;      // Send start bit
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
}
// Function Purpose: I2C_ReStart sends start bit sequence
void I2C_ReStart(void)
{
    RSEN = 1;      // Send Restart bit
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
}
//Function : I2C_Stop sends stop bit sequence
void I2C_Stop(void)
{
    PEN = 1;      // Send stop bit
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
}
//Function : I2C_Send_ACK sends ACK bit sequence
void I2C_Send_ACK(void)
{
    ACKDT = 0;      // 0 means ACK
    ACKEN = 1;      // Send ACKDT value
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
}
//Function : I2C_Send_NACK sends NACK bit sequence
void I2C_Send_NACK(void)
{
    ACKDT = 1;      // 1 means NACK
    ACKEN = 1;      // Send ACKDT value
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
}
// Function Purpose: I2C_Write_Byte transfers one byte
unsigned char I2C_Write_Byte(unsigned char Byte)
{
    SSPBUF = Byte;    // Send Byte value
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
    return ACKSTAT;    // Return ACK/NACK from slave
}
// Function Purpose: I2C_Read_Byte reads one byte
unsigned char I2C_Read_Byte(void)
{
    RCEN = 1;      // Enable reception of 8 bits
    while(!SSPIF);    // Wait for it to complete
    SSPIF = 0;      // Clear the flag bit
    return SSPBUF;    // Return received byte
}
Başlık: Ynt: I2C Yardım
Gönderen: ziyaretci - 29 Mayıs 2022, 03:16:19
Merhaba.

Sensöre nasıl istek gönderileceği ya da cevap bekleneceği, ilgili sensörün datasheet'inde detaylıca yazar. Ki bahsettiğiniz fonksiyonlar(drivers) öyle elzem kodları veya algoritmaları içerisinde barındıran fonksiyonlar değildir. Neredeyse çoğu denetleyicide bu protokol donanımsal olarak gerçekleştirilir.

Bu protokolü kolayca anlamak için en basitinden harici bir eeprom'un 2402 vs. datasheet'ini incelemek yeterlidir.

Genel olarak yapı:
-Master cihazdan slave cihaza bilgi ya da yazma komutu gönderilir.
-Yazılacaksa komutun hemen ardından slave cihazın talep ettiği kodlar yollanır.
-Okunacaksa ilk önce okunmak istenen datanın belirtilen talep kodu yollanır, cevap beklenir.
-Hattın meşgul olduğu, yazma işleminin bitip bitmediği ek kontrollerdir.

Örneğin; 2402'yi ele alacak olursak, bu belleğin 3 bit adres tanımı mevcuttur. A2-A1-A0, siz bu pinlere donanımsal olarak lojik bilgiyi verdiğinizde otomatik olarak yazılım tarafında bir tanımlama yapmış oluyorsunuz. Bu adresleri ek entegreler(IC) kullanarak isterseniz dinamik hale getirebilirsiniz. Bu uygulamanın ihtiyacına yönelik değişir.

Ek A başı{
Sizin kullandığınız slave cihaz üreticisi adres için size fiziksel pin(giriş) sağlamamış olabilir.  Bu durumda size bir dayatma sunar. Sen bu cihaz ile bilgi alışverişi yapmak istiyorsan, adresin BU der. Çünkü dahili olarak bu adresi çipe gömmüştür. Bu dayatma sadece, tasarlanan sistemin revizyonu için uyumsuzluk riski taşır. Örneğin siz ana kartınızdaki harici belleğinize 000 adresi adamışsınızdır. İş bu ya sonra aynı hattan fiziksel adres pinleri olmayan bir sensör ile haberleşeyim dersiniz bir bakmışsınız üretici onun adresini de 000 yapmış. Buyrun cenaze namazına gibi... Lehim işi, falçata işi, köprü işi... Tabi bu işlemler(kesme, biçme vs.) sivil elektronik cihazlar için ya da belli bir noktadaki endüstriyel cihazlar için kısa yolla çözüme kavuşturur. Her ne kadar içiniz rahat etmesede. Yoksa bir adres pini için ana kart revizyonu gerekebilir. Bu yüzden tasarlanan kartlar olabildiğince opsiyonel bir şekilde tasarlanmalı.
Ek A sonu}

Hazır bu şekilde yüzeysel olarak I2C protokolünden bahsetmişken, örnek bir kod üzerinden inceleme gerçekleştirelim:

///////////////////////////////////////////////////////////////////////////
////   Library for a MicroChip 24LC16B                                 ////
////                                                                   ////
////   init_ext_eeprom();    Call before the other functions are used  ////
////                                                                   ////
////   write_ext_eeprom(a, d);  Write the byte d to the address a      ////
////                                                                   ////
////   d = read_ext_eeprom(a);  Read the byte d from the address a     ////
////                                                                   ////
////   b = ext_eeprom_ready();  Returns TRUE if the eeprom is ready    ////
////                            to receive opcodes                     ////
////                                                                   ////
////   The main program may define EEPROM_SDA                          ////
////   and EEPROM_SCL to override the defaults below.                  ////
////                                                                   ////
////                            Pin Layout                             ////
////   -----------------------------------------------------------     ////
////   |                                                         |     ////
////   | 1: NC   Not Connected | 8: VCC   +5V                    |     ////
////   |                       |                                 |     ////
////   | 2: NC   Not Connected | 7: WP    GND                    |     ////
////   |                       |                                 |     ////
////   | 3: NC   Not Connected | 6: SCL   EEPROM_SCL and Pull-Up |     ////
////   |                       |                                 |     ////
////   | 4: VSS  GND           | 5: SDA   EEPROM_SDA and Pull-Up |     ////
////   -----------------------------------------------------------     ////
////                                                                   ////
///////////////////////////////////////////////////////////////////////////
////        (C) Copyright 1996, 2003 Custom Computer Services          ////
//// This source code may only be used by licensed users of the CCS C  ////
//// compiler.  This source code may only be distributed to other      ////
//// licensed users of the CCS C compiler.  No other use, reproduction ////
//// or distribution is permitted without written permission.          ////
//// Derivative programs created using this software in object code    ////
//// form are not restricted in any way.                               ////
///////////////////////////////////////////////////////////////////////////


#ifndef EEPROM_SDA

#define EEPROM_SDA  PIN_E0
#define EEPROM_SCL  PIN_E1

#endif


#use i2c(master, sda=EEPROM_SDA, scl=EEPROM_SCL)

#define EEPROM_ADDRESS unsigned int16
#define EEPROM_SIZE    1024

void init_ext_eeprom() {
   output_float(EEPROM_SCL);
   output_float(EEPROM_SDA);
}

int1 ext_eeprom_ready() {
   int1 ack;
   i2c_start();            // If the write command is acknowledged,
   ack = i2c_write(0xa0);  // then the device is ready.
   i2c_stop();
   return !ack;
}

void write_ext_eeprom(EEPROM_ADDRESS address, BYTE data) {
   while(!ext_eeprom_ready());
   i2c_start();
   i2c_write((0xa0|(BYTE)(address>>7))&0xfe);
   i2c_write(address);
   i2c_write(data);
   i2c_stop();
}


BYTE read_ext_eeprom(EEPROM_ADDRESS address) {
   BYTE data;

   while(!ext_eeprom_ready());
   i2c_start();
   i2c_write((0xa0|(BYTE)(address>>7))&0xfe);
   i2c_write(address);
   i2c_start();
   i2c_write((0xa0|(BYTE)(address>>7))|1);
   data=i2c_read(0);
   i2c_stop();
   return(data);
}


Mesela yukarıdaki kodda 4 fonksiyon ile haberleşme sağlanmış.

-Kurulum veya gerekli başlangıç ayar ya da tanımlamaları(init)
-Slave cihazın hazır olup olmadığını kontrol eden fonksiyon(ready)
-Yazma fonksiyonu(write)
-Okuma fonksiyonu(read)

Öncelikle şuraya ST'nin bir datasheet'ini bırakayım:
https://www.st.com/resource/en/datasheet/m24c16-f.pdf (https://www.st.com/resource/en/datasheet/m24c16-f.pdf)

Bu datasheet'in 6. sayfasında BUS ile ilgili yani fiziksel haberleşme yoluyla ilgili teknik bilgiler verilmiş.
Örneğin SDA pinini lojik-0'a çekip, SCL pininden 1 saykıl(clock) gönderirsen haberleşmeyi başlatırsın, daha sonra göndermek isteğin her data'yı bit seviyesinde SDA pinine aktarıp slave cihazın o seviyeyi bilgi olarak kaydetmesi için SCL'den clock yollarsın. Sonra protokol dahilinde her 8 bit yollandıktan sonra slave cihaz SDA pinini data'yı aldım diye lojik-0 seviyesine çeker. Bu şu demek oluyor. Sen master cihazdan SCL'yi yönetirken 9. clock'u gönderdiğinde SDA pini input olarak okunmalı. Eğer ACK sinyali alınmazsa belliki slave cihaz ya meşguldür ya da bozuktur. Meşgul olduğunu varsayarsak belli periyotlar dahilinde tekrardan yoklama çekmelisiniz.

Sayfa 7'de, slave cihaz üreticisinin sabiti, donanımsal adres tanımı ve protokolün işlem modu seçimi yer almaktadır.

Yani isterseniz siz bu işlemi donanıma bırakmayıp, bu clock ve bilgileri kendiniz de high-low yaparak ve okuyarak bu işlemi gerçekleştirebilirsiniz.

Bu noktada ext_eeprom_ready(); fonksiyonunu inceleyeyim. Bakalım ne yapmışlar?
1. I2C_start demiş. Yani SDA hattını lojik-0 seviyesine çekmiş.
2. Sonra I2C_write fonksiyonuna 0xA0 parametresi girmiş. Buradaki MSB(yüksek değerlikli) 4 bit "Device type identifier(1)" sabiti, LSB 4 bitin MSB 3 biti donanımsal adres, 0. biti yazma veya okuma bildirimi biti.
Bu satırda yapılan 3 bitlik donanımsal adresin 0 olduğu cihaza yazma işlemidir. Bu fonksiyonun içerisinde 0xA0(0b1010 000 0) bitleri donanımsal olarak SDA ve SCL pinleri üzerinden yukarıda bahsettiğim(datahseet'te bahsedilen) gibi aktarılmıştır. Yani fonksiyonun içinde IDE sağlayıcı her ne kadar gizlese de pin high-low işlemleri var.
Aynı fonksiyonun sonunda 9. clockta slave cihazın cevabını bir return yapan bir komut var. Bunu,  tanımlanan 1 bitlik ACK değişkeninden anlayabiliriz.
3. I2C_stop SDA hattı ve SCL hattı lojik-1 seviyesine çekilmiş.

Mantık bu şekilde. Yani çip üreticisi bize ne yapmamız gerektiğini a-dan z-ye anlatmış.

Peki bu iş donanımsal olarak MCU'larımız tarafından nasıl yapılıyor derseniz o da şu şekilde; MCU'nuzda adres register'ı, data register'ı(belli bir önbelleğe sahip) yer alır. Siz sadece iki register'a ilgili değeri yazıp, MCU dahilinde yer alan kontrol register'ındaki bir bit ile dataları ateşlersiniz. Ne SDA ne de SCL hattını lojik-1 yapayım, lojik-0 yapayım diye uğraşmazsınız. Çünkü bu işlemlerin protokolü sabit olduğundan dolayı zaten donanımsal olarak MCU içerisindeki ilgili donanım birimi dahilinde olan D-JK FF(Flip-Flop)vs. yapılara kazınmıştır. Her şey otomatik olarak gerçekleşir. Size dilediğiniz zaman ilgili register'dan data'yı işlemek kalır.

Örneğin sizin MCU'ile sıcaklık verisini alıp ekrana otomatik olarak bastırmanız gibi. Sürekli aynı işlemi yapmıyorsunuz değil mi?(İnsan olarak)

Ve MCU bu işlemi donanımsal özelliğinden dolayı bağımsız olarak gerçekleştirdiği için, fiziksel haberleşme, MCU tarafında yapmanız gereken önemli işlemleri sekteyi uğratmaz. Sisteminiz daha performanslı çalışır.

Durum aslında, yapısal bilgilerin bileşiminden, bir araya getirilip anlamlandırılmasından oluşmaktadır. Puzzle parçalarını birleştirdikten sonra ortaya çıkan resim gibi. Bu en yüksek seviyeli CPU'da da böyledir. En pasif olan IC'de de böyledir.

Umarım faydalı olur. İyi çalışmalar dilerim.