AVR - Attiny 13A-AHT10 ile Sıcaklık ve Nem Ölçümü

 


Attiny13A Attiny ailesinin  küçük ve nispeten ucuz üyelerindendir. Datasheete buradan ulaşabilirsiniz. Attiny 45 de olduğu gibi bunda da tam olarak yerleşik UART,SPI ve TWI (I2C) birimleri yoktur. Attiny 45 de en azından USI ile tamamen olmasa da donanımla çalışan TWI ve SPI bulunmaktaydı. 64 byte SRAM ve 1024 byte Flash hafıza ile oldukça zorlayıcı bir mikro denetleyici olduğunu söylemeliyim. Bu deneyimler optimizasyon konusunda oldukça faydalı oldu. Yazılımla çalışan UART-I2C ile uğraşırken bu haberleşme yöntemlerinde de eksiklerimi gidermeme yardımcı oldu. Daha önce I2C kütüphanesi yazmıştım ama bazı sorunları vardı. Örneğin iletişim koptuğunda sonsuz döngüye girmiyordu ama iletişim tekrar başlamıyordu. Buradaki hatalarımı gördüm ve o yazdığım kütüphaneleri de güncelledim. AHT10 için yazdığım konuda datashetteki formülü aynen kullanmıştım. Burada tek bir float değişken dahi tanımlayacak hafıza olmadığından değişiklik yapmak zorunda kaldım. İlk olarak yazılımla çalışan UART ile başlayayım.

Software UART 



Attiny 45 için de bir software UART kütüphanesi yazdım orada birçok hesaplamayı kodların içinde yaptım ama burada önceden yapıp yazmam gerekti malum hafıza kısıtlı. Ama ben bu hesaplamaları anlatmadan geçmeyeceğim.  Bu hesaplamalardan kastım Baud Rate için gerekli olan hesaplamalardır. Baud rate saniyede giden bit sayısıdır. UART iletişimde çoğunlukla start, 8 bit veri ve stop olmak üzere 10 bit veri gönderilir. Parity veya bir den fazla stop biti eklenmiş hali dışında 8 bitten daha fazla verinin gönderildiği hali de kullanılabilir ama bunlara girmeyeceğim. Sıklıkla kullanılan Baud rate değeri 9600 bps üzerinden ilerleyeceğim. 9600 bps, bir saniyede giden bit sayısıdır. Bu durumda bir bit için gereken süre 1/9600 sn dir. Bu işlem sonucunu µs cinsinden yazmak için 1/9600*1000000 haline getirirsek bir bit için 104,166 µs gerekir.

UART iletişimi öncesi veri hattı yüksek "1" durumundadır. Hattın düşük "0" duruma geçmesi Start koşuludur. Bundan sonra 8 bit giden verinin ilk biti yani "bit0" başta "bit7" sonda olacak şekilde hattı "1" veya "0" yapar. Son olarak hattın "1" olması Stop koşuludur. Bu çerçeve (frame) içinde gerçekleşen "1" ve "0" olma durumlarının her birisi için geçmesi gereken süre 104µs dir. UART ile iletişim için saat sinyali taşıyan bir hat gerekli değildir. iki tarafın doğru haberleşmesi için önceden belirlenen baud rate değerine göre her bir bit için gereken süre doğru olmalıdır. Aksi halde iletişim başarısız olacaktır. 


Burada 'A' harfi gönderilmiştir. A dec 65= hex 0x41=bin 0b0100 0001 dir. Start sonrası bit0 "1" olduğundan hattı "1" yapmıştır. İlk olarak düşük öncelikli bitten başlanarak veri gönderilir. Son bit sonrası hat tekrar "1" yapılarak stop biti yazılmıştır. gördüğünüz gibi bir bit için gereken süre 104µs dir. Bu bazen 2-3µs farklı sürelerle gitse de çerçeve dışına taşmadıkça sorun olmayacaktır. Şekilde her bir veri bitinin ortasında bulunan nokta karşı tarafın veriyi okuduğu yerdir. Bu noktalar sınırı aşarsa iletişim başarısız olur.


Yukarıdaki veriyi alan kısımda baud rate 10600 olarak değiştirdim. Bu durumda bir bit için gereken süre 94,339µs olacaktır. Giden taraf 104µs bekler ve bit değişimi yapar ve frame için 1040µs gerekir. Alan taraf 943µs içinde iletişim bitmesini ve stop bitini beklerken 'A' için bit 7 "0" olduğundan stop gerçekleşmemiş olarak görür dolayısıyla çerçeve hatası oluşur. Bu zamanlamalar özellikle yüksek hızlarda önemlidir.

Aplikasyon notlarında olduğu gibi bir bit için gereken bekleme süresini ayarlamak için timer kullanmayı denedim ama yüksek hızlarda tutarsız çalıştı. Bunu farklı yöntemlerle yapmak gerekti. Bir iki farklı kütüphane inceledim onlardan bazıları çalışmadı bazıları assembly dili ile yazılmış kodlar kullanıldığı ve benim hiç bir bilgim olmadığı için örnek alamadım. Klasik olarak delay.h ve tam istediğim olmasına rağmen builtins.h kütüphaneleri sabit sayılar istediğinden kullanamadım. İşlemciyi istenen çevrim kadar bekleme yaptırmasından ve değişken değer aldığından delay_baisc.h kullanmaya karar verdim. Normalde delay kullanmak doğru değil ama burada donanım ile yapabileceğim bir birim yok mecburen delay kullanacağım.

Veri Gönderme (Tx)

Yazılımla çalışan UART için en kolay kısım gönderim kısmıdır. Gönderime başlamadan kesmelerin kapatılması gerekiyor, bir kesme oluşursa bit sürelerinde kayma oluşabilir. Gönderim için bir pini çıkış yap, sürekli yüksek tut, gönderim zamanı "0" yap bekle giden verinin ilk bitini kontrol et "1" ise "1" "0" ise hattı "0" yap. 8 bit için bu beklemeyi ve kontrolü yap hattı tekrar "1" yap ve bitti. Peki bu beklemeyi nasıl belirleyeceğiz. İşlemcinin çalışmasını kontrol eden ve her işlem için gereken süreyi belirleyen dahili bir saat kaynağı bulunmaktadır. Bu MCU ya harici bir osilatör bağlamak çok mantıklı değil zaten toplam 8 adet pin var. Bu dahili osilatör kaynak olacak şekilde sigorta ayarlarıyla işlemcinin çalışma frekansı ayarlanabiliyor. 
Öncelikle CKSEL1 "1" -CKSEL0 "0"yaparak 9,6MHz frekansını seçiyoruz. CKDIV8 "0" yaparak çıkış frekansını 8 e bölüyoruz. Böylece işlemcinin çalışma frekansı 1.2MHz olarak ayarlanıyor. CKDIV8 yerine CLKPR içindeki bitleriyle de istenen değerlere ayarlama yapılabilir. Örnek olarak 9,6MHz prescaler olarak 4 seçilmiş hali için gerekli kodlar:
CLKPR=(1<<CLKPCE);
CLKPR=(1<<CLKPS1);
 Ben neden bu frekansı seçtiğime daha sonra değineceğim.1.2MHz ile bir çevrim için gereken süre1/1.200.000= 0,833333..µs dir.  9600bps de bir bit için 104µs gerekiyordu. Buna göre 104,166/0,833333..=125 çevrimdir. Burada formülü bulmak için işlemleri birleştirelim. (1/9.600)/(1/1.200.000)=(F_CPU)1.200.000/ (BAUD)9.600=125 sonucunu buluruz. Delay_basic.h de tanımlı "_delay_loop_2()" fonksiyonu her bir değer için işlemciyi 4 çevrim boyunca bekletir. _delay_loop_2(1); dediğimiz zaman geçen süre 4*0,833333=3,333µs olacaktır. Bu durumda bulduğumuz çevrim değerinin 4 te birini fonksiyona parametre olarak girmemiz gerekir. Sonuç olarak bir bit için gereken bekleme süresini (Tx_delay) bulmak için formül Frekans/Baud/4 olur. Bekleme dışında yapılan kontrol ve duruma göre ayarlı pinin "1" veya "0" yapılması da bir zaman alacaktır.

Bu gereken zamanı fazladan eksilecek çevrim değeri olarak formülde işleyeceğim. Bunu deneme yanılma yöntemiyle değil lojik analizörle kontrol ederek yaptım. Derleyicinin ne kadar bir çevrim ile kodu işleyeceğini assembly bilmediğim için kontrol edemedim. Pin değişimi ve kontrol için gereken çevrim sayısını "3" olarak buldum ve böylece tx_delay= (F_CPU/BAUD/4)-3 oldu. Bütün bunların sonucunda gönderme için gereken fonksiyonu bu şekilde tanımladım.

void uart_gonder(uint8_t uData){        
        _delay_loop_2(tx_delay);
        TX_LOW;//start koşulu
        _delay_loop_2(tx_delay);//start için gerekli bekleme
        cli();//kesmeler kapatıldı
        for (uint8_t i=0;i<8;i++){//8 bit için tekrar
                if (uData&0x01){//ilk bit kontrol edildi
                        TX_HIGH;//ilk bit "1" ise "1"
                        }else{
                        TX_LOW;//ilk bit "0" ise "0"
        //burada else gerekli değil ama
        //"0" olması durumunda zamanlama yani gerekli çevrim değişmesin diye eklendi
                }
                _delay_loop_2(tx_delay);//her bir bit için gerekli bekleme
                uData>>=1;//veri bir bit kaydırıldı
        }        
        TX_HIGH;//stop koşulu
        _delay_loop_2(tx_delay);//stop için gereken bekleme
        sei();//kesmeler açıldı
}

Veri Alma (Rx)

Gönderim basit ve çok yüksek hızlarda bile daha hatasız olduğu halde alma kısmı o kadar basit ve hatasız değil. En büyük sorun işlemcinin düşük frekansta çalışıyor olması bu nedenle belli bir hızın üstünde veri alımı yapamayız. Veri alırken işlemcinin başka satırları yürütüyor olması ihtimaline karşı kesme kullanmaktan başka çare yok. Bunun için en iyi yöntem dış kesme kullanımı ama ne yazık ki bir tane dış kesme pini var ve onu da kullanarak MCU nun kolunu bacağını kesmek istemedim. ayrıca farklı pinleri seçme özgürlüğü olması için pin değişim kesmesini kullandım. Bu kesmenin de sorun hem yüksek hem düşük durumda kesme oluşturmasıdır. Bunu da kesme girişinde pinin "0" olmasını kontrol ederek ve  "0" ise yani start koşulu oluşmuşsa döngüyle tüm iletişim sonuna kadar işlem yaparak çözdüm. Kesme içinde döngü kullanmak doğru değil ama burada alternatif yok. Aksi halde iletişim gerçekleşmez. 

Alma sırasında yaşadığım bir diğer zorluk karşı taraftan kaç tane verinin geleceğini bilmeyişimdir. Bunu bilmeden örneğin gelen veriyi gönder gibi bir kodun çalışması sorun oluyor. Stop için gereken süreyi artırdığımda bu sorunu çözüyorum ama bu seferde yüksek hızlarda problem oluşuyor. Ben burada yüksek hızlarda veri alacak şekilde bir ayarlama yapmaya çalıştım.

Gönderme sırasında bir bit için beklediğimiz gibi alma sırasında da aynı süreyi kontrol etmek gerekiyor. Start sonrası bekleyip verinin ilk bitini kontrol edip geçici değişkene yazmamız gerekir. Start sonrası 104µs bekleyip hat "1" ise "1" yazmak doğru olmaz. Verinin ilk bitinin ortasında okuma yapmak doğru olacaktır. Öncelikle kesme ayarlarını yaptım, kesme oluşunca eğer hat "0" ise yani start oluşmuşsa işlem yapılacak. Sonrasında tx_delay kadar bir bekleme yapılarak 8 defa hat kontrol edilecek. Sonrasında stop için bir bekleme yapılarak okunan veri değişkene yazılıp kesmeden çıkacak. Bu kontrol işlemini yapmadan önce doğru zamanlamayı yakalamak için veri okuma kısmında bir pini toggle ile "1" ve "0" yaptım. Aşağıda bunu görebilirsiniz.

ISR(PCINT0_vect){                
        if (RX_PIN_LOW){//start kontrol pin "0" ise
                _delay_loop_2(tx_delay);
                for (uint8_t i=0;i<8;i++){                        
                        _delay_loop_2(tx_delay);
                        PORTB^=(1<<4);//zamanlama icin kulandım        
                }
                _delay_loop_2(tx_delay);
        }
}



Burada da 'A' harfi gönderdim, ilk bit için start sonrası geçen süre 104µs olurken kesme sonrası aynı miktar bekleme yapılmasına rağmen az bir gecikme oluşmuş ve çıkış olarak ayarladığım alttaki ikinci hattın ilk "1" olma zamanı 124,6µs olmuştur. Bu anda hattı kontrol edip veriyi yazmış olsaydım ilk bit için belki şansa doğru okuma yapardım ama kırmızı kutu içine aldığım 6 ve 7 numaralı bitleri doğru okuyamazdım. Ayrıca tx_delay için yaptığım hesaplama burada doğru sonuç vermedi  101µs gibi bir süre gerçekleşti. Bunu tam doğru olması için başka bir değişken daha tanımlayıp yeni bir hesaplama yapmam gerekecek. Bu alma kısmı olduğundan Rx_delay diyeceğim. Yine start sonrası okuma yapma zamanımı yani alttaki çıkışın ilk "1" olduğu zamanı üstte gelen verinin ilk biti içindeki noktayı yakalaması için bir gecikme daha gerekecek. Bunun için de yarım_rx_delay isimli bir bekleme daha hesaplayacağım.

İlk olarak rx_delay=(F_CPU/BAUD/4)-(çevrim sayısı) ile rx delayı bulabilirim. Tx ten tek farkı yapılan işlemlerde geçen çevrim sayısının farklı olmasıdır. Yarım rx için yarım_rx_delay=(F_CPU/BAUD/4)/2-(çevrim sayısı) formülünü kullanabilirim. Rx delayın yarısı diyemem çünkü  çevrim sayısı farkı olacaktır. Ayrıca her ikisi içinde bir sorun var baud rate belli değerin üstüne çıkınca sonuç 0 olacaktır. Bu frekansta 115200 zor olduğundan daha düşük değerlerde rx ve tx delay için sorun yok ama rx yarısı dediğimde yarım rx 0 olacaktır. Bunun için eğer yarım_rx 1 den küçükse 1 yap gibi bir koşul eklemesi yapmak gerekiyor. Bu şekilde eklemeler yapınca aldığım sonuç:


Görüldüğü gibi artık çıkışın "1" ve "0" olduğu anda veriyi okuduğumda sorun olmuyor. Start sonrası ilk biti de tam zamanında yakalamış oldum. Artık pin toggle yaptığım kısma hattın durumunu kontrol eden ve buna göre değişkenin ilgili bitini değiştiren kodları ekleyebilirim. Bu değişkeni kesme çıkışında oluşturduğum ring buffera kaydederek sonrasında istediğim gibi işleyebilirim.

Bu arada yukarıda bahsettiğim sorun için bir Stop_rx_delay ilavesi gerekiyor. Okuma sonrası hemen veri göndermek istendiğinde sorun çıkmaması için gerekli. Gönderim yaparken kesmeler kapatıldığından bu gerekiyor. Onu da hesaplamak için aynı formülü kullandım ama bunda yüksek hızlarda daha sorunsuz olması için çevrim sayısını artırdım. Tx e girmeden ilave beklemeler ekledim böylece bir çeşit çözüm buldum ama en iyisi veriyi alır almaz gönderim yapmamaktır.

ISR(PCINT0_vect){                
        if (RX_PIN_LOW){//start kontrol pin "0" ise        
                _delay_loop_2(yarim_rx_delay);//ilk bit için bekleme
                for (uint8_t i=0;i<8;i++){                        
                        _delay_loop_2(rx_delay);//her bir bit için gereken bekleme
                        veri>>=1;//geçici değişken kaydırıldı
                        //PORTB^=(1<<4);//zamanlama icin kulandım                
                        if (RX_PIN_HIGH){// hat kontrolü yapılıyor
                                veri|=0x80;//"1" ise "1"                                
                        }else{//zamanlama dengesi için eklendi
                                veri&=~0x80;//"0" ise "0" yazılıyor                                
                        }//*/                
                }
                _delay_loop_2(stop_rx_delay);// hemen sonrasında gönderime geçmemek için
                rx_bas=(rx_bas+1) & UART_Rx_Mask;//ring buffer indis arttı
                rx_ring[rx_bas]=veri;//ring buffera yazıldı
        }
}

Uart Başlatma

İlk yazacağımı sona bırakmış gibi oldum ama gerekli işlemleri neden yaptığımı anlatmadan buna girmek doğru olmayacaktı. İlk olarak Rx ve Tx pinleri giriş-çıkış yapılıyor. Rx için gerekli kesme ayarları yapıldıktan sonra gerekli bekleme hesaplamaları yapılıyor.  Burada 4 çevrim halinde gidildiği için tam nokta atışı sonuç çıkmıyor. Sabit değerli bir iletişim için Attiny 13A kullanacağım zaman builtins.h ile tam çevrim sayısıyla işlem yaparım. Bu şimdilik işimi görecek seviyede çalışıyor. Tx 38400 den başlıyor. Rx ne yazık ki o hızda sadece tek karakter alarak çalışıyor. 19200 bps ve altı daha sağlıklı çalışıyor.4800bps gibi düşük bir hızda okuma sonrası hemen gönderim sorun çıkartıyor. Stop delay artarsa yani çevrim sayısı azalırsa sorun çözülür ama yüksek hızlarda sorun olacaktır. 

Burada görüldüğü gibi daha alım devam ederken gönderim için fonksiyona giriyor burada kesme kapandığı için arada kaçan veriler oluyor.
İşlemci frekansı artarsa 57600bps ya kadar çıkabilir ama bu başka bir sorun çıkartıyor. Sadece UART kullanacaksam işlemci frekansını 9,6MHz olarak ayarlayabilirim. Sonuçta çok mükemmel çalışmasını beklemiyorum zaten, sadece gönderim amaçlı kullanacağım ve onu da gayet iyi başarıyor.


void uart_basla(Bd_rate_t _baud){
        cli();
        RX_HIGH;
        RX_IN;//pin giriş yapıldı
        TX_HIGH;
        TX_OUT;//pin çıkış yapıldı
        PCMSK|=(1<<RX_);//rx pini için kesme ayarlandı
        GIMSK|=(1<<PCIE);
        tx_delay = ((F_CPU / _baud) / 4)-3;
        if (tx_delay<=1){
                tx_delay=1;
        }
        yarim_rx_delay=(F_CPU/_baud/8)-7;
                if (yarim_rx_delay<=1){
                        yarim_rx_delay=1;
                }
        rx_delay=(F_CPU/_baud/4)-4;
                if (rx_delay<=1){
                        rx_delay=1;
                }
                stop_rx_delay=(F_CPU/_baud/4)-10;
                if (stop_rx_delay<=1){
                        stop_rx_delay=1;
                }        
        sei();
}


Software I2C

UART konusu kadar detaya girmeyeceğim çünkü önceki konularda bahsettiğim şeylerin tekrarını yapmış olurum. Start-Stop koşulu, ACK-NACK ve verinin örnekleme zamanları hakkında yazmıştım. Burada yazılımla çalışan  I2C de tek karışık olan oluşturduğum transfer fonksiyonudur. Her zaman olduğu gibi aplikasyon notlarını ve başka kütüphaneleri inceledim. ACK kontrolü yapılmadan çözülmüş örnekler gördüm. Benim yazdığımda bu şekilde değil. Donanım tabanlı olan nasıl çalışıyorsa bu da o şekilde çalışıyor. 

İşlemcinin çalışma frekansını 1,2MHz e düşürmemin nedeni I2C çalışma frekansını tutturmak içindir. Aksi halde burada da beklemeler kullanmak durumunda kalacaktım. Frekansı düşürünce sadece çıkış yönlendirme pin kontrol veri kaydırma gibi işlemler için harcanan çevrim süresi 100kHz seviyesini yakalamasam da yaklaşmamı (66kHz) sağladı. Diğer tüm frekanslar ya çok yüksek ya da düşük olacaktır. Frekansı 9,6MHz olarak ayarlasaydım. Attiny 45 USI biriminde olduğu gibi her saat darbesi arasında 4µs lik beklemeler eklemem gerekirdi. Bekleme ilaveleri sınırlı olan hafızayı doldurduğu için kullanmamak en doğrusu oldu. UART için de aynı şansım olsaydı Baud rate değerine bakmaz kullanırdım ama ne yazık ki çok yakın değerleri bulmak zorunlu olduğundan orada bekleme kullanmaksızın işlem yapmak imkansız. UART ile sonuçtan çok memnun olmasam da I2C nin çalışmasından memnunum.

I2C Transfer

Start-Stop ve ACK-NACK nedir daha önce bahsetmiştim. Attiny 45 USI biriminde bu işleri zaten yazılımla yürütüp sadece sayaç ve kesmelerle donanımı kullanmıştım. Burada yine aynı şekilde bu koşulları oluşturdum. Donanım olmadığından verinin gönderilmesini UART gibi döngü içinde yaptım. Bu işlemi yine UART gibi veriyi kontrol edip "1" veya "0" olması durumuna göre hattı "1" veya "0" yaparak gerçekleştirdim. Burada bir fark ilk gidenin 7 numaralı bit olmasıdır. Onun dışında UART ile aynıdır. Adresin ve verinin gönderilmesi aynı olduğundan ikisini ayrı ayrı gönderecek kod yerine adres sonrası veri yazma durumu varsa yapılanı tekrar etmesi için bir Do while döngüsü içine aldım.

Yazma

                                I2CDR=i2c_ring[i2c_son++];                                
                                for( uint8_t i=0;i<8;i++){
                                        if (I2CDR&0x80){
                                                SDA_HIGH;
                                                }else{
                                                SDA_LOW;
                                        }
                                        SCL_HIGH;
                                        while (!(PINB&(1<<SCL)));
                                        I2CDR<<=1;
                                        SCL_LOW;
                                }
                                SDA_IN;
                                SCL_HIGH;
                                I2CDR=I2C_PIN;
                                SCL_LOW;
                                SDA_OUT;
Sırasıyla yapılanı anlatayım. Öncelikle bu paylaştığım satırlara start koşulu sonrası giriliyor. I2CDR isimli bir değişkene gidecek olan veriyi yazdım bu adres veya başka bir veri de olabilir ama adres olduğunu düşünelim. Sonrasında 8 defa aynı işlemi gerçekleştirmek için For döngüsü içine giriyor. Burada gidecek olan verinin 7 numaralı biti sorgulanıyor. Bu bitin "1" veya "0" olması durumuna göre SDA "1" veya "0" yapılıyor. Doğru zamanlama için else ekledim. SCL hattı "1" yapılıyor ve "1" olana kadar döngüde kalıyor. Bu gerçekleşince karşı taraf veriyi okuduğu için I2CDR bir bit kaydırılıyor ve SCL "0" yapılıyor. Bu işlem tüm bitler gidene kadar tekrar ediliyor. Sonrasında 9. bit ACK kontrolü olduğundan SDA giriş yapılıyor. SCL "1" yapılınca karşı tarafın cevabı yani hattın durumu PIN registeriyle I2CDR ye yazılıyor. Seçilen pin hangisi olursa buna göre kaydedilmiş oluyor. SCL hattı"0" yapılıp sonraki işlem için SDA çıkış yapılıyor.

Bundan sonrası ACK-NACK durumuna ve yazma-okuma görevine göre değişiyor. ACK ve yazma için tüm bu işlemlerin gidecek veriler bitene kadar tekrar etmesi gerekiyor. Bunun için tüm bu işlemi bir döngü içine alıyorum.

do{
               //yukarıdaki kodlar 

 }while (!(I2CDR&I2C_NACK)&&(i2c_son<i2c_bas)&&(i2c_task== I2C_WRITE));
        

While içinde ilk olarak ACK-NACK durumu sorgulanıyor. Bunun için I2CDR ile PIN durumu kaydı kontrol ediliyor. I2C_PIN-PINB ve I2C_NACK-(1<<SDA) olarak tanımlıdır. NACK durumunda döngüden çıkar. Sonraki kontrol gidecek verinin sonuna gelinip gelinmediğidir. başka veri kalmamışsa yani baş ve son indisi eşitse döngüden çıkar. Son olarak yazma veya okuma görevi kontrol edilir. İlk olarak adres yazıldı ama sonrasında okuma yapılacaksa döngüden çıkar ve alt satırdaki başka bir döngü içinde gerçekleşen okuma kısmına geçilir. Okuma kısmına geçmeden adres sonrası ACK yerine NACK alınmışsa bu alt satıra girmez veya yazma tamamlanmışsa yine alt satıra girmez ve stop koşulu gerçekleşir. Adres sonrası ACK alınmışsa okuma için alt satırdan devam eder.

Okuma

                        SDA_IN;
                        for( uint8_t i=0;i<8;i++){
                                I2CDR<<=1;
                                SCL_HIGH;
                                if (I2C_SDA_HIGH){
                                        I2CDR|=0x01;
                                        }else{
                                        I2CDR&=~0x01;
                                }
                                SCL_LOW;
                        }
                        i2c_ring[i2c_bas++]=I2CDR;
                        SDA_OUT;
                        if (i2c_bas!=i2c_len){
                                SDA_LOW;//ack gonder
                                }else{
                                SDA_HIGH;//son veride nack gonder
                        }
                        SCL_HIGH;
                        SCL_LOW;

İlk olarak veri okunacağından SDA giriş yapılır. 8 bit veri için For döngüsüne girer ve verinin yazılacağı I2CDR bir bit kaydırılır. Bu kaydırma işlemini önceden yapmış gibi görünmesine aldanmayın aslında boş bir kaydırmadır. Veriyi okuyup kaydırsam çıkışta ilk okunan veriyi de kaydırmış olur ve bir bit kaybederdim. SCL bu sefer önceden "1" yapılıyor ki karşı taraf veriyi hatta yazabilsin ve biz de doğru zamanda hat kontrolü yapalım. SDA hattının durumuna göre I2CDR nin 0 numaralı biti "1" veya "0" yapılıyor. I2C_SDA_HIGH-I2C_PIN&(1<<SDA) olarak tanımlı yani PIN registeri kontrol ediliyor. SCL hattı "0" yapılıp tekrar başa geçiliyor. Tüm bitler okunduktan sonra okunacak başka veri yoksa NACK varsa ACK göndermek için yani hattı "1" veya "0" yapmak için SDA yeniden çıkış yapılıyor. Alınacak veri boyutu ile alınan veri boyutu karşılaştırılıp gerekli işlem yapılıyor. SCL hattı karşı taraf ACK durumunu kontrol etmesi için "1" ve "0" yapılıyor. Eğer verinin sonuna gelinmişse NACK ile stop koşuluna geçilirken sona gelinmemişse aynı işlemin tekrarı için bir Do while döngüsü çalıştırılır.

do{
            //yukarıdaki kodlar
}while (i2c_bas!=i2c_len);

Burada while içinde alınacak veriyle alınan veri indisi karşılaştırılır eşit olduğunda okuma işlemi tamamlanmış demektir. Bir önceki adımda NACK ile işlem sonlanır ve stop koşulu oluşur. Bunun dışında tüm fonksiyonlar aynı olduğundan burada tekrar etmeyeceğim. 

Bu kütüphaneye ve tüm kodlara bu linkten  ulaşabilirsiniz. AHT10 ile yapılan hesaplamaların karışık olduğunu düşünmüyorum bu nedenle uzun uzun değinmeye gerek yok. Bu arada birçok yerde ana fonksiyonlar yerine alt fonksiyonları kullandım bunun nedeni hafızadan kazanmaktır. Bu çalışmada da optimizasyon için OS seçildi aksi halde sığma şansı yok.

Bağlantı Şeması: