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