AVR- I2C (TWI) AHT10 Kullanımı Bölüm 2

 Önceki bölümde TWI biriminde bulunan registerler ile veri yazma ve okuma işlemini yaparak çalışma mantığını anlatmış oldum. Bu bölümde bu işlemleri birer fonksiyon halinde derli toplu bir hale getireceğim. TWI kesmesiyle yazma-okuma yaparak AHT10 ile sıcaklık ve nem bilgisini alacağım.

Fonksiyonlar

Başlatma, start ve stop için burada tekrar söyleyecek bir şey yok. Kesme kullanacağım için sadece TWIE bitini "1" yapacak kodu ekliyorum. Bu fonksiyonları aşağıda görebilirsiniz. Veri okuma ve yazma kısmında işler biraz farklı olduğundan bu fonksiyonlara geçiyorum.

Veri Yazma

Bunun için birkaç farklı fonksiyon tanımladım. Adres ve komut bilgisi sonrası göndereceğim veriyi bir tane veya birden fazla olacak şekilde göndermemi sağlayan fonksiyonlar oluşturdum. Fonksiyonların ilk parametresi slave aygıtın adresidir. Yazma olduğu için komut belli bunda bir parametre oluşturmaya gerek yok. Bir byte veri göndereceğim fonksiyonda gerekli değil ama birden fazla olacaksa ne kadar göndereceğimi de parametre olarak girmem gerekir. Dizi oluşturup gönderir ve boyutu için NULL (0x00) karakterine kadar gönder diyebilirim ama sensörler NOP (0x00) boş komut isteyebilirler. Bu durumda NOP gönderme şansım olmaz. Önceden tanımlı sensör komut dizisi göndermek için de bir fonksiyon oluşturdum. Bir diğer parametre tekrarlayan başlangıç (repeated start) için gereklidir. Stop koşulu oluşturmadan yazma veya okumaya devam etmek için kullanabiliriz.


Burada ilk veri 0x61 gittikten sonra stop koşulu gerçekleşmiş sonraki yazma komutu ve adres başlangıcı için start oluşmuş ve sonraki veri yazılmış.


Burada ilk veri 0x61 gittikten sonra stop koşulu gerçekleşmemiş sonraki yazma komutu ve adres başlangıcı için start oluşmuş ve sonraki veri yazılmış. Bu durumu gerçekleştirmek için bir değişken kullandım. İki tip fonksiyon için en önemli parametre yazacağımız veridir. Bir tane olduğunda sorun yok ama birden fazla olduğunda bu verileri bir bellekte tutmamız ve yazma işini oradan yürütmemiz gerekir. Belleğin neresinde olduğumuzu da bilememiz ve bunun için de bir işaretçi oluşturmamız gerekir.

Daha önce UART kullanımı ile ring buffer konusunu yazmıştım burada tekrar etmeyeceğim. Fonksiyona parametre olarak gelen verileri belleğe birer birer kaydederken verinin kayıt yerini tutan işaretçiyi de birer birer artırıyorum. Bu işlemin ne kadar olacağı ya NULL karaktere ya da dizinin boyutunu bildirdiğim ve parametre ile gösterdiğim boyutla sınırlı olacaktır. Bu ana fonksiyonlar bir önceki konuda alt başlıklarla ifade etmeye çalıştığım start, adres ve data gibi alt fonksiyonlardan oluşacaktır. İlk olarak adres ve komutu (R/W) parametrelere bağlı olarak bir değişkene kaydediyorum. Sonraki adım olarak veriyi belleğe alıyorum. En son olarak start koşulu oluşturarak TWI birimi iletişimi başlatıyor.

Alt Fonksiyonlar

void i2c_start(){//start koşulu için gerekli bitler 1 yapıldı
	TWCR=(1<<TWEN)|(1<<TWSTA)|(1<<TWINT)|(1<<TWIE);
	while(!(TWCR & (1<<TWINT)));// twint beklendi
}
void i2c_adr(uint8_t adr,i2c_cmd_t cmd){	
	Sladr_RW=((adr<<1)|cmd);//adres bir bit sola kaydırıldı ve komutla değişkene yazıldı
}
void i2c_data(uint8_t data){
	i2c_tx_bas=(i2c_tx_bas+1)&I2C_Tx_Mask;//veri işaretçisi artırıldı
	i2c_tx_ring[i2c_tx_bas]=data;//işaretçinin gösterdiği belleğe veri yazıldı
}
void i2c_end( i2c_rep_t repst){
	i2c_rep=repst;//repeated start kontrol
	i2c_start();//start koşulu
}
void i2c_stop(){
	TWCR=(1<<TWEN)|(1<<TWSTO)|(1<<TWINT)|(1<<TWIE);// stop koşulu
}

 

Sadece bu fonksiyonlar ile veri göndermek mümkün. Sırasıyla adres, data (veri miktarı kadar) ve end fonksiyonu çağrılarak işlem yapılır. Burada görünmeyen asıl işlemler TWI kesme rutini içindedir. Ana fonksiyonlara göz atıp kesme içinde neler olduğunu anlatmaya çalışacağım. Bir sensörün veya farklı bir entegrenin komut setinde çeşitli veriler olacaktır. Bunları söylediğim sırayla tek tek yazmak yerine bir fonksiyon halinde birleştirmek daha doğru olur. Bu amaçla üç tane fonksiyon oluşturdum. Bunlardan biri tek bir veri için, diğer ikisi birden fazla veri göndermek içindir. Karakter dizisi göndermek için tanımladığım fonksiyona belki hiç gerek olmayacak.

Ana Fonksiyonlar

void i2c_send_data(uint8_t adr, uint8_t data,  i2c_rep_t repst){	
	i2c_adr(adr, I2C_WRITE);
	i2c_data(data);	
	i2c_end(repst);
}
void i2c_send(uint8_t adr, uint8_t* str, uint8_t len,  i2c_rep_t repst){
	i2c_adr(adr, I2C_WRITE);
	for (uint8_t i=0;i<len;i++){//dizi boyutu kadar
		i2c_data( str[i]);// belleğe yazar
	}
	i2c_end(repst);
}
void i2c_send_str(uint8_t adr, const char* str,  i2c_rep_t repst){
	i2c_adr(adr, I2C_WRITE);
	while (*str){//0x00 bulana kadar
		i2c_data(*str++);// belleğe yazar		
	}	
	i2c_end(repst);	
}

Veri Okuma

Okuma kısmında bir  fonksiyon yeterli oluyor. Hangi adresten ne kadar okuyacağımızı parametre olarak aktardığımız fonksiyon ilk olarak adres ve komutu (R/W) değişkene yazıyor. Veri miktarını tutan bir başka değişkene kayıt yapıp start koşuluna gidiyor. Burada tüm iş kesme rutini içinde yapılıyor. Gelen veri belleğe kaydediliyor. Bu kayıt işlemi içinde bellekteki yeri tutan işaretçi artırılıyor. Bu sayede verinin alındığını öğreniyoruz. Bellekten okuma yapmak için de farklı fonksiyonlar oluşturdum.

TWI okuma

void i2c_read(uint8_t adr, uint8_t len){	
	i2c_adr(adr, I2C_READ);
	i2c_rx_len=(i2c_rx_len+len)&I2C_Rx_Mask;//okunacak veri sınırı
	i2c_start();	
}

Bellekten Okuma

uint8_t i2c_gelen(){//bellek işaretçileri eşit değilse yeni veri gelmiştir
	if (i2c_rx_son==i2c_rx_bas)	return 0;
	return 1;	
}
uint8_t i2c_oku(){// bellek işaretçileri eşit olana kadar bellekten veri alır.
	i2c_rx_son=(i2c_rx_son+1)&I2C_Rx_Mask;
	return i2c_rx_ring[i2c_rx_son];
}

Bu fonksiyonlardan sonra kesme rutini içinde neler oluyor onlara değinelim.

TWI Kesme(Write)

Ana program içinde farklı işlemler yapmak için yoklama yerine kesmeleri tercih ediyorum. Bu sayede MCU daha verimli şekilde kullanılmış oluyor. Burada kullanmamam gerektiği halde while döngüleri kullandım. TWI kesme vektörü son sıralarda olduğu için biraz rahat davrandım. Başka bir işlemi engelleme olasılığı daha az olacaktır. Ayrıca bu döngülerde bekleme süresi çok düşük. Eğer arka arkaya veri okuma yazma yapmayacaksanız aralarda farklı işlemler ve hatta bekleme süreleri olacaksa bu döngülere kesme kullandığımız için gerek yoktur. Öncelikle alttaki lejantı paylaşayım.


Datasheetteki şemalar için hazırlanmış bir lejant, şemalar oldukça anlaşılır. Bunların dışında adım adım neler yapılması gerektiğini gösteren farklı tablolar da mevcut. Ama bunun yerine bu şemaları paylaşmak daha mantıklı geldi.


Bu şemada hangi işlem sonrası hangi durum kodunun oluştuğu görülmektedir. Bu kodlara göre kesme rutini içinde yapacağımız işlemlere karar vereceğiz. TWI birimi her işlem sonunda bir durum kodu oluşturduğu gibi kesme bayrağını da "1" yapıyor. Bunu elle temizlememiz gerektiğini önceki konuda söylemiştim. Her seferinde kesme oluyor biz siliyoruz. Bu şekilde yukarıda görülen adımlar arsında sürekli kesme oluşuyor. Kesme rutini içine girdiğimizde hangi adımda olduğumuzu durum kodu ile belirlemek için Switch-case kullanılmış ben de önceki konularda benzer şeyler yapmıştım. Burada Switch ifadesi olarak TWSR registerini 3-7 bitlerine bakacağız.

#define I2C_STATUS_MASK					0xF8//tws7-tws3
#define I2C_STATUS					(TWSR&I2C_STATUS_MASK)

I2C_STATUS kesmeye girdiğimizde hangi seçeneğe gideceğimizi yukarıdaki şemaya göre şekillendirecektir.

Start

İlk adım olan Start ile 0x08 değerini alır. Bu bize start koşulunun gerçekleştiğini söyler. Bu durumda adres ve komutu TWDR ye yazmamız gerekir. Ardından TWINT "1" yaparak işlemi tamamlarız. Yukarıda bahsettiğim tekrarlayan başlangıç durum kodu 0x10 dur. Bu gerçekleştiğinde farklı bir durum kodu olmasına rağmen yapılması gereken işlemler aynıdır. Start ve Rep_Start için aynı kodu ayrı ayrı yazmak yerine iki seçeneği alt alta yazıp 0x08 içine break; yazmıyorum. Böylece kesme içine girdiğimiz zaman durum kodu 0x08 (START) veya 0x10 (REP_START) olsa da tek bir işlem yapılıyor.

case (I2C_START):
case (I2C_REP_START): 
	TWDR=Sladr_RW;//adres ve komut yazıldı
	TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE);//OR ile twint "1" yapmak da yeterlidir.			
		while(!(TWCR & (1<<TWINT)));// iletişim aralıkları sık değilse gereksiz.
	break;

 

TWINT  "1" yapıldıktan sonra kesme while döngüsü olmazsa kesme içinden çıkıp ana döngüye geri döner. Eğer döngüyle beklenirse sonraki adıma kadar kesmede kalır ve sonraki adım için oluşan kesme gerçekleşir. Fakat iletişim koparsa sonsuz döngüye girer burada en hızlı haliyle denemek için böyle yaptım.

Adres

Slave aygıt adresi algılayıp ACK ile dinlemeye başladığını belirtmiş olur. Bu gerçekleşmezse NACK koşulu oluşur. ACK için ( (MTR_ADR_ACK) 0x18, NACK için (MTR_ADR_NACK) 0x20 kodu oluşturulur. NACK durumunda hattı meşgul etmemek için stop koşuluna geçilebilir veya aynı iletişimi tekrar deneyebiliriz. Bu tekrarı bir değişkenle kontrol etmesek sonsuz bir döngüye girebiliriz. ACK durumundaysa veriyi hatta göndermemiz gerekir. Veri tek bir byte ile sınırlıysa burada yapılacak işlemler vardır ama birden çok olduğunda veri yazma sonrası farklı durum kodu oluşur ve burada yapılacak işlemin aynısı orada da yapılması gerekir. Yine kod kalabalığı olmaması için adres ACK ve data ACK alt alta yazıp 0x18 içine break; yazmıyorum. Bunun yerine case (I2C_MTR_ADR_ACK | I2C_MTR_DATA_ACK): başka bir durum koduyla çakışmamış olsaydı yazabilirdik. Fakat bu çakışma olduğu için bu yöntemi yapamıyorum.

Data

Adres sonrası ACK ile data sonrası ACK işlemleri aynıdır. Burada öncelikli olarak bellekte göndereceğimiz verinin sonuna gelip gelmediğimizi kontrol ediyoruz. Sona gelmemişsek  TWDR ye gidecek olan veriyi bellekteki yeriyle eşitleyerek yazıyoruz. Ardından yine TWINT "1" ve döngüyle kontrol ediyoruz. Haliyle veri hatta yazılıyor ve yine kesme oluşuyor. Burada datanın alınması veya alınmamasına bağlı olarak ACK (MTR_DATA_ACK) 0x28 veya NACK (MTR_DATA_NACK) 0x30 koşulu oluşuyor. NACK durumunda adres NACK ile aynı şeyleri yapıyoruz. Data ACK durumunda yine aynı yere gelip tekrar bellekteki veriyi kontrol ediyoruz. Sona geldiğimizde yani gidecek veri kalmadığında iki seçenek belirliyoruz. Eğer bir Rep_Start ayarlıysa start koşulu oluşturuyoruz. tekrar bir başlangıç ayarlı değilse stop ile işlemi sonlandırıyoruz.

		case (I2C_MTR_ADR_ACK):				
		case (I2C_MTR_DATA_ACK):
			if (i2c_tx_son!=i2c_tx_bas){//bellek sonu kontrol ediliyor
				i2c_tx_son=(i2c_tx_son+1)&I2C_Tx_Mask;
				TWDR=i2c_tx_ring[i2c_tx_son];//veri yazılıyor
				TWCR |=(1<<TWINT);				
				while(!(TWCR & (1<<TWINT))){
					timeout++;
					if (10>=timeout){
						timeout=0;
						break;;
					}
				}
			}
			else if (i2c_rep==1){//repeated start
				i2c_rep=0;
				TWCR|=(1<<TWSTA);				
			}
			else{				
				i2c_stop();
			}
			break;	
		case I2C_MTR_ADR_NACK:	
			//tekrar dene, dur v.b
			break;				
		case I2C_MTR_DATA_NACK:	
			//tekrar dene, dur v.b
			break;

Master mod veri yazma kesmelerle bu şekildedir. Arbitration çoklu master olan bir hat için geçerli olduğundan bu konuya girmiyorum. Hattın kontrolünü başka bir master aygıt aldığında yapılması gerekenler için bu kodlara göre işlem yapılır.

TWI Kesme(Read)

Yazma kısmında olduğu gibi öncelikle datashette yer alan şemayı paylaşıyorum. Burada yine her adımda oluşturulan durum kodlarını ve neler yapılması gerektiği görünüyor.


Veri okumak için adresi TWDR ye yazıp kaç adet veri okumak istediğimizi belirleyip start koşulu ile iletişimi başlatıyorduk. Bu start veya rep_start bölümüyle ortaktır. Durum kodları da aynıdır.

Adres

Adres ve komutu (Sladr_RW) start-rep_start  seçeneğinde yazıyorduk. Bu komut okuma olduğu için sonrasında adres ACK -NACK durum kodu farklı olacaktır. Yazmada bu adımdan sonra 0x18 veya 0x20 durum kodları oluşturulurken, okumada adres ACK (MRD_ADR_ACK) 0x40 ve adres NACK (MRD_ADR_NACK) 0x48 durum kodu oluşturulur. NACK durumunda hattı meşgul etmemek için stop koşuluna geçilebilir veya aynı iletişim tekrar deneyebiliriz.  ACK durumunda yani slave aygıtın cevap vermesi durumunda veriyi okumaya başlamamız gerekiyor. Bunun için TWINT "1" yapıyor ve kontrol ediyoruz. Veri alındıkça ACK koşulunu oluşturan bu sefer master cihaz olacaktır. Son sıradaki veri alınınca NACK koşulu oluşturarak iletişimi sonlandıracağız. Bu nedenle TWINT ile beraber TWEA da "1" yapılıyor.  Tek bir byte veri alacaksak adres ACK cevabından sonra alınacak veri adetini sorgulamamız gerekir.  Sonraki adımda veriyi almaya başlıyoruz. Tekrar bir sorgulama yapmak gerekeceğinden veri alma koşulunun adres ACK koşulunun üstüne yazarak komut tekrarı yapmıyoruz.

Data

Adres ACK sonrası data ACK (MRD_DATA_ACK) 0x50 durumu oluşur TWDR belleğe kaydedilir. TWINT ve TWEA "1" yapılır. Yukarıda bahsettiğim gibi bir veya birden fazla olma durumunu kontrol için aynı kodları yazmak yerine adres ACK durumuna geçilir. Son veri alınınca NACK koşulu oluşturulur. Bu durumda data NACK (MRD_DATA_NACK) 0x58 durum kodu oluşur ve TWDR den son veri belleğe alınarak iletişim sonlandırılır.

		case I2C_MRD_DATA_ACK:		
			i2c_rx_bas=(i2c_rx_bas+1)&I2C_Rx_Mask;//bellek işaretçisi artıldı
			i2c_rx_ring[i2c_rx_bas]=TWDR;// veri belleğe alındı			
		case I2C_MRD_ADR_ACK:
			if (i2c_rx_bas!=i2c_rx_len){//okunacak veri sınırı kontrolü
				TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE)|(1<<TWEA);//ACK
				
				while(!(TWCR & (1<<TWINT)));
				}else{
				TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE);//NACK
				
				while(!(TWCR & (1<<TWINT)));				
			}			
			break;			
		case I2C_MRD_ADR_NACK: 	
			i2c_stop();
			break;		
		case I2C_MRD_DATA_NACK:
			i2c_rx_bas=(i2c_rx_bas+1)&I2C_Rx_Mask;
			i2c_rx_ring[i2c_rx_bas]=TWDR; 
			i2c_stop();
			break;

 

Bundan sonraki durum kodları slave aygıt ve hata kodları içindir. Aşağıda toplu halde tüm durumlara ve başlık dosyasına ulaşabilirsiniz.

AHT10

Üreticinin sitesinde datasheet yok. Güncellendiği yazılı başka kaynaklardan  çevrilmiş halini buldum buradan ulaşabilirsiniz. Bu çevirilerde eksik bilgiler var. Bu nedenle biraz daha araştırdım ve üreticinin sayfasında yönlendirmediği bir  Çince datasheet buldum. Burada eksik kalan komutlara ulaştım. AHT10 ±0,3 °C hata payı, 0,01 °C çözünürlükte ve -40+85 °C aralığında sıcaklık ölçebiliyor. ±2 %RH hata payı, 0,24%RH çözünürlükte ve 0-100%RH aralığında nem ölçümü yapabiliyor. Sıcaklık ve nem belli bir aralık dışına çıkınca hata payı artıyor. AHT10 en fazla 3,6 V ile çalışabiliyor ama ben modül olarak aldım ve modül 6V a kadar çalışabiliyor. Modül üstünde SDA-SCL pinleri TWI iletişimi için ve VIN-GDN besleme pinleri olarak çıkartılmış. ADR pini lehimli bu nedenle modülün adresi 0x38, lehimli değilse adres 0x39 olur.

İletişim

I2C (TWI) ile bağlantı kurulabilen bu modülün komut sayısı az veya datasheette yazılı değil. Bu kanıya nereden vardığımı merak edenler için. Modülün durum kodlarında ismi geçen komut, çevrim ve normal (CMD,CYC ve NOR) modlara nasıl geçildiği yazılı değil. Bunlar ölçüm anında da geçilen değişen modlar değil. Bu durumu kontrol ettim daima normal mod dönüşü aldım. İngilizce datasheette init kısmı da eksikti bu bölümü Çince datasheetten aldım.


Datasheeten alıntı tablo ile devam edersek üç adet komut bulunmaktadır. İlk elektrik verildikten sonra 40ms bekleyip init komutu ile başlatma tavsiye edilmiş. Modülün kalibrasyonu için State bit 3 kontrol edilmeli denilmiş. Bu kontrol yerine soft reset ile başlıyorum. Bunun için önce adres 0x38 ve yazma komutu sonra reset komutu 0xBA gönderiyoruz. Bundan sonra başlatma komutu göndereceğiz. ilk olarak adres 0x38 ve yazma komutu sonra init komutu 0xE1, ilk parametre 0x08 ve son parametre 0x00 gönderiyoruz. Ölçüm için tetikleme komutu 0xAC adres sonrası gönderiyoruz. İlk parametre 0x33 ve son olarak 0x00 ile tetikleme başlatılıyor. Bundan sonra 75ms beklememiz öneriliyor. Bu beklemeyi yapmadan da okumak mümkün sadece bir önceki çevrimin sonuçlarını almış oluruz.

Okuma yapmak için adres ve okuma komutu gönderip 6 byte veri alıyoruz. ilk byte veri[0] durum kodlarını içeriyor. Durum kodlarından bit 7 meşgul bayrağıdır. Tetik sonrası bekleme yapmadan okuduğunuz zaman "1" bekleme sonrası okuduğunuz zaman "0" olacaktır. Bit 6-5 numaralı mod gösterir ama hep "00" olarak gördüm değişim için bilgi verilmemiş. Bit 3 kalibrasyon durumunu gösterir "0" ise kalibrasyon tamamlanmamış demektir. İnit komutu ile başlatma önerilmiş. Sonraki iki byte veri[1],veri[2]ve 3. byte veri[3]yüksek değerli 4 biti nem için ayrılmış. 3. Byte veri[3] düşük değerli 4 biti ile son iki byte veri[4],veri[5] sıcaklık içindir. Bu şekilde okunan veriyi bir değişkene kaydedip sıcaklık-nem dönüşüm hesabı için kullanacağız.

Sıcaklık

3. byte yarısı ve son iki byte ile aldığımız veriyi 32 bit bir sayı olarak saklıyoruz. Bunun için son byte veri[5] en sağda olacak. Veri[4] 8 bit sola kaydırıp veri[3] ilk 4 biti 16 bit sola kaydırıp sayımızı oluşturuyoruz.

Bu formüle göre işlemi yaparak sıcaklık değerini buluyoruz. St okuduğumuz veri değeridir. Sıcaklık için virgülden sonrası önemli değilse 20 bit sağa kaydırıp işlem yapabiliriz. Önemliyse bölme işlemini yaparak float değer olarak sonucu  sıcaklık değişkenine atıyoruz.

Nem

Nem için de sıcaklık gibi sayımızı oluşturmamız gerekiyor. Bunun için son byte veri[3] en sağda olacak. Veri[2] 8 bit sola kaydırıp veri[1] 16 bit sola kaydırıp sayımızı oluşturuyoruz. Veri[3] ilk 4 biti sıcaklık için ayrılmış bu nedenle oluşan sayıyı 4 bit sağa kaydırmamız gerekiyor.

Yine formüle göre işlemi yaparak nem değişkenine atıyoruz. Modül hakkında söylenecek başka bir şey kalmadı.  Her şeyi toplu olarak aşağıda paylaşıyorum.

Güncelleme Notu:

Bir önceki konuda arka arkaya veri yazma ve okuma konusunda sorun olduğu için while döngüsü kullandığımı yazmıştım. Birkaç yerde birden kullanmak yerine tek bir döngü kullanarak aynı sonucu elde ettim. Bunun için bir meşgul bayrağı tanımladım. Tüm iletişimin bitmesini bekleyip bundan sonra sıradaki iletişimi başlattım.

Bunun dışında tekrar başlatma kısmında start koşulunu kaldırdım. Meşgul bayrağı "0" olunca fonksiyon start koşulunu oluşturacak. Burada kesmeyi de kapattım. Sürekli olarak kesme içine giriyor ve 400kHz de sorun oluyordu.

Son haliyle dosyalar aşağıdaki gibidir.

 

/*
 * aht10_i2c.c
 *
 * Created: 24.01.2022 00:56:31
 * Author : haluk
 */ 

//#include <xc.h>
#include <avr/io.h>
#include <util/twi.h>
#include "uart.h"
#include "i2c_master.h"
#include <util/delay.h>
#include <stdio.h>

uint8_t aht_adr=0x38;//aht10adres
uint8_t aht_res=0xBA;//soft reset
uint8_t aht_init[]={0xE1,0x08,0x00};//init
uint8_t aht_trig[]={0xAC,0x33,0x00};//ölçüm başlatma
uint8_t aht_data[6];
uint32_t aht_temp=0;
uint32_t aht_hum=0;
float sicaklik=0.0;
float nem=0.0;
char str_data[20];

int main(void)
{	
    uart_basla(115200);
    i2c_init();	
	
	i2c_send_data(aht_adr,aht_res, N_REPEAT);//soft reset
	i2c_send(aht_adr,aht_init,3, N_REPEAT);//init komutu
	uint8_t i=0;
    while (1){
		i2c_send(0x38,aht_trig,3,N_REPEAT);//ölçme tetik
		//_delay_ms(75);//75ms bekleme önerilmiş	
		i2c_read(aht_adr,6);// veri okuma		
		while (i2c_gelen()){//okunan veri alındı
			aht_data[i++]= i2c_oku();
		}
		i=0;
		aht_temp=((uint32_t)(aht_data[3]&0x0F)<<16)|((uint16_t)aht_data[4]<<8)|(aht_data[5]);
		aht_hum=(((uint32_t)aht_data[1]<<16)|((uint16_t)aht_data[2]<<8)|aht_data[3])>>4;
		sicaklik=((float)aht_temp/5242.88)- 50;//formülün sadeleştirilmiş hali
		//sicaklik=((aht_temp*25)>>17)- 50;//virgülden sonrası önemsizse
		nem=((float)aht_hum/10485.76);//formülün sadeleştirilmiş hali
		//nem=(aht_hum*25)>>18;//virgülden sonrası önemsizse	
		sprintf(str_data,"%.2f--%.2f\n",sicaklik,nem);//sonuçları ekrana yazmak için
		uart_dizi(str_data);					
		//_delay_ms(500);//ölçüm aralığı		
    }
}

//////////////////////////////////////////////////////////////////////////////////
/*
 * i2c_master.c
 *
 * Created: 3.02.2022 13:55:35
 *  Author: haluk
 */ 

#include "i2c_master.h"

static volatile uint8_t i2c_rx_bas=1,i2c_rx_son=1,i2c_tx_bas=1,i2c_tx_son=1,i2c_rx_len=0;
static volatile uint8_t i2c_rx_ring[I2C_Rx_Boyut];
static volatile uint8_t i2c_tx_ring[I2C_Tx_Boyut];
static volatile uint8_t Sladr_RW=0, i2c_rep=0;
static volatile i2c_state_t i2c_state=I2C_READY;
volatile uint32_t timeout=0;
void i2c_init(){
	cli();
	TWSR|=(0<<TWPS0)|(0<<TWPS1);//prescaler 1
	TWBR=((F_CPU/F_SCL)-16)>>1;//i2c scl frekans
	TWCR=(1<<TWEN)|(1<<TWIE);//i2c kesme açık ack açık i2c açık (1<<TWEA)|	
	sei();
}
void i2c_disable(){
	TWCR=0x00;
}
void i2c_start(){
	i2c_state= I2C_BUSSY;//iletişim başladı meşgul bayrağı 
	TWCR=(1<<TWEN)|(1<<TWSTA)|(1<<TWINT)|(1<<TWIE);
}
void i2c_adr(uint8_t adr,i2c_cmd_t cmd){
	while (i2c_state!=I2C_READY){//hazır olana kadar bekle
		timeout++;		
		if (timeout>I2C_TIMEOUT){
			timeout=0;
			return;
		}
	}	
	Sladr_RW=((adr<<1)|cmd);
}
void i2c_data(uint8_t data){
	i2c_tx_bas=(i2c_tx_bas+1)&I2C_Tx_Mask;
	i2c_tx_ring[i2c_tx_bas]=data;
}
void i2c_end( i2c_rep_t repst){
	i2c_rep=repst;	
	i2c_start();
}
void i2c_stop(){
	TWCR=(1<<TWEN)|(1<<TWSTO)|(1<<TWINT);
	i2c_state= I2C_READY;
}
void i2c_send_data(uint8_t adr, uint8_t data,  i2c_rep_t repst){	
	i2c_adr(adr, I2C_WRITE);
	i2c_data(data);
	i2c_end(repst);
}
void i2c_send(uint8_t adr, uint8_t* str, uint8_t len,  i2c_rep_t repst){
	i2c_adr(adr, I2C_WRITE);
	for (uint8_t i=0;i<len;i++){
		i2c_data( str[i]);
	}
	i2c_end(repst);
}
void i2c_send_str(uint8_t adr, const char* str,  i2c_rep_t repst){
	i2c_adr(adr, I2C_WRITE);
	while (*str){
		i2c_data(*str++);		
	}	
	i2c_end(repst);	
}
void i2c_read(uint8_t adr, uint8_t len){
	i2c_adr(adr, I2C_READ);
	i2c_rx_len=(i2c_rx_len+len)&I2C_Rx_Mask;
	i2c_start();	
}
uint8_t i2c_gelen(){
	if (i2c_rx_son==i2c_rx_bas)	return 0;
	return 1;	
}
uint8_t i2c_oku(){
	i2c_rx_son=(i2c_rx_son+1)&I2C_Rx_Mask;
	return i2c_rx_ring[i2c_rx_son];
}
ISR(TWI_vect){
	switch(I2C_STATUS){
		case (I2C_START):
		case (I2C_REP_START): 		
		TWDR=Sladr_RW;
		TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE);
		break;
		case (I2C_MTR_ADR_ACK):	
		case (I2C_MTR_DATA_ACK):			
			if (i2c_tx_son!=i2c_tx_bas){
				i2c_tx_son=(i2c_tx_son+1)&I2C_Tx_Mask;
				TWDR=i2c_tx_ring[i2c_tx_son];
				TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE);
			}
			else if (i2c_rep==1){
				i2c_rep=0;
				i2c_state= I2C_READY;
				TWCR=(1<<TWEN);									
			}
			else{								
				i2c_stop();
			}
			break;	
		case I2C_MTR_ADR_NACK:
			i2c_stop();	
			//tekrar dene, dur v.b
			break;				
		case I2C_MTR_DATA_NACK:	
			i2c_stop();
			//tekrar dene, dur v.b
			break;
		case I2C_ARB_LOST: 			
			break;
		case I2C_MRD_DATA_ACK:
			i2c_rx_bas=(i2c_rx_bas+1)&I2C_Rx_Mask;
			i2c_rx_ring[i2c_rx_bas]=TWDR;			
		case I2C_MRD_ADR_ACK:
			if (i2c_rx_bas!=i2c_rx_len){
				TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE)|(1<<TWEA);
				}else{
				TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWIE);
			}			
			break;			
		case I2C_MRD_ADR_NACK: 	
			i2c_stop();
			break;		
		case I2C_MRD_DATA_NACK:
			i2c_rx_bas=(i2c_rx_bas+1)&I2C_Rx_Mask;
			i2c_rx_ring[i2c_rx_bas]=TWDR; 
			i2c_stop();
			break;
		case I2C_STR_ADR_ACK:
		break;					
		case I2C_STR_ADR_ACK_M_ARB_LOST:		
		break;	
		case I2C_STR_DATA_ACK:				
		break;
		case I2C_STR_DATA_NACK:				
		break;
		case I2C_STR_DATA_ACK_LAST_BYTE:	
		break;
		case I2C_SRD_ADR_ACK:	
		break;				
		case I2C_SRD_ADR_ACK_M_ARB_LOST:	
		break;	
		case I2C_SRD_GEN_ACK:	
		break;				
		case I2C_SRD_GEN_ACK_M_ARB_LOST:	
		break;	
		case I2C_SRD_ADR_DATA_ACK:
		break;			
		case I2C_SRD_ADR_DATA_NACK:		
		break;	
		case I2C_SRD_GEN_DATA_ACK:	
		break;		
		case I2C_SRD_GEN_DATA_NACK:
		break;			
		case I2C_SRD_STOP:		
		break;				
		case I2C_NO_INFO: 
		break;
		case I2C_BUS_ERR:
		break;
	}
}
/////////////////////////////////////////////////////////////////////////
/*
 * i2c_master.h
 *
 * Created: 3.02.2022 13:55:35
 *  Author: haluk
 */ 

#ifndef I2C_MASTER_H
#define I2C_MASTER_H


#define F_CPU 16000000UL
#define F_SCL 100000 
//#include <xc.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define I2C_Rx_Boyut					16
#define I2C_Tx_Boyut					16
#define I2C_Rx_Mask						(I2C_Rx_Boyut-1)
#define I2C_Tx_Mask						(I2C_Tx_Boyut-1)
#define I2C_TIMEOUT						1000
//I2C status
#define I2C_STATUS_MASK					0xF8//tws7-tws3
#define I2C_STATUS						(TWSR&I2C_STATUS_MASK)
#define I2C_START						0x08// gönderim hazır
#define I2C_REP_START					0x10// tekrar gönderim //sonrası adres ister
//I2C master status
#define I2C_MTR_ADR_ACK					0x18// adres yazıldı ack geldi
#define I2C_MTR_ADR_NACK				0x20// adres yazıldı nack geldi
#define I2C_MTR_DATA_ACK				0x28// veri gitti ack geldi
#define I2C_MTR_DATA_NACK				0x30// veri gitti nack geldi
#define I2C_MRD_ADR_ACK					0x40//adres yazıldı ack geldi
#define I2C_MRD_ADR_NACK				0x48// adres yazıldı nack geldi
#define I2C_MRD_DATA_ACK				0x50// veri alındı ack gönderildi
#define I2C_MRD_DATA_NACK				0x58//veri alındı nac gönderildi
//I2C slave status
#define I2C_STR_ADR_ACK					0xA8
#define I2C_STR_ADR_ACK_M_ARB_LOST		0xB0
#define I2C_STR_DATA_ACK				0xB8
#define I2C_STR_DATA_NACK				0xC0
#define I2C_STR_DATA_ACK_LAST_BYTE		0xC8
#define I2C_SRD_ADR_ACK					0x60
#define I2C_SRD_ADR_ACK_M_ARB_LOST		0x68
#define I2C_SRD_GEN_ACK					0x70
#define I2C_SRD_GEN_ACK_M_ARB_LOST		0x78
#define I2C_SRD_ADR_DATA_ACK			0x80
#define I2C_SRD_ADR_DATA_NACK			0x88
#define I2C_SRD_GEN_DATA_ACK			0x90
#define I2C_SRD_GEN_DATA_NACK			0x98
#define I2C_SRD_STOP					0xA0
//I2C error status
#define I2C_ARB_LOST					0x38//hat yönetim kaybı
#define I2C_NO_INFO						0xF8//belirsiz durum
#define I2C_BUS_ERR						0x00//hat hatası
//////////////////////////////////////////////////////////////////////////
typedef enum{
	DISABLE			=0x00,
	ENABLE			=0x05,
	CL_FLAG			=0x85,
	NACK			=0x85,
	STOP			=0x95,
	START			=0xA5,
	STO_STR			=0xB5,
	ACK				=0xC5	
}i2c_twcr_t;
typedef enum {
	I2C_WRITE		=0,
	I2C_READ		=1,
}i2c_cmd_t;
typedef  enum {
	_REPEAT			=1,
	N_REPEAT		=0,
}i2c_rep_t;
typedef  enum {
	I2C_READY		=0,
	I2C_BUSSY		=1,
}i2c_state_t;
void i2c_init();
void i2c_disable();
void i2c_start();
void i2c_stop();
void i2c_adr(uint8_t adr,i2c_cmd_t cmd);
void i2c_data(uint8_t data);
void i2c_end( i2c_rep_t repst);
void i2c_send_data(uint8_t adr, uint8_t data,  i2c_rep_t repst);
void i2c_send(uint8_t adr, uint8_t* str,uint8_t len,  i2c_rep_t repst);
void i2c_send_str(uint8_t adr, const char* str,  i2c_rep_t repst);
void i2c_read(uint8_t adr, uint8_t len);
uint8_t i2c_gelen();
uint8_t i2c_oku();

#endif

 

 

Hiç yorum yok:

Yorum Gönder