AVR- Buzzer ile Müzik



Sadece birkaç değişik "bip" sesi çıkartmak amacıyla başladığım iş biraz büyüdü. Bip sesi yerine farklı notaları farklı hızlarda çalsam derken bana göre güzel bir şey çıktı. Okuyan birçok kişi için basit gelebilir ama ben yine de paylaşmak istedim. Çünkü hem ilerde tekrar aynı şeyleri araştırmak zorunda kalmamak için yazmış olurum hem de ihtiyaç duyanlara bir kaynak olur. Bu arada gerçekten çocuk gibi eğlenerek yaptığım bir iş oldu. Arduino Tone() fonksiyonunu muhtemelen herkes kullanmıştır veya duymuştur. Onda bazı sorunlar var. Birden fazla kart ve timer ile çalışacak şekilde ayarlandığından oluşan bu sorunlar burada yok. Mesela Tone ile arada bekleme olmadan ses çıkartamazsınız benim paylaşımımda nota aralarına bekleme koymasanız da çalışır. Buna ek olarak aynı notayı farklı oktav değerlerinde çalmak isterseniz tüm notaları tekrar yazmanız gerekmez sadece parametre değiştirirsiniz. Tek tek anlatmak yerine konuya geçmek daha doğru olacak.

Notalar

Ses basit bir anlatımla bir yüzeyin belirli bir frekansla titreşimiyle oluşur. 20 ile 20000Hz arasındaki titreşimlerin çıkardığı sesi duyabiliyoruz. Yaşlandıkça bu üst sınır düşüyor. Çocuklarla test ettik, onların duyduğu sesi ne yazık ki ben duyamıyorum,  15000 Hz sonrası sessizlik. Bu geniş aralıkta duyulan ve müzik aletlerinin çıkardığı kulağa hoş gelen kısmını 0 dan başlayıp 8 e kadar oktav denilen bölümlere ayırmışlar. Tam ortada yer alan 4 oktav "LA" notasının frekansı 440 Hz kabul edilmiş ve her oktav 12 ye bölünmüş. La notasının frekansına göre bir sonraki notayı bulmak için 2^(1/12) ile çarpmak veya bölmek gerekiyor. Ayrıca La notasının bir alt veya üst oktav frekansı 2 katı veya yarısı şeklinde ilerliyor. Bunları öğrenince bir tablo ile tüm notaların frekanslarını hesapladım. Hazır olarak bulduğum değerlerde virgülden sonrası yeterli gelmedi.



La notası frekansı için 432Hz değerinin kullanıldığı enstrümanlar ve müzik türleri varmış ama ben o konulara girmedim, komplo teorilerine kadar gidiyor. Bu tabloda görülen Hz cinsinden frekansa göre benim çıkışı ayarlamam gerekecek.

Zamanlayıcı

Notalar için Timer1 birimini kullandım. Bunun nedeni timer1 in 16 bit olmasıdır. Bu sayede notaların gerçek frekansına yaklaşmış oldum. Başta PWM ile yaptım ama farklı pinleri seçme şansı vermek için CTC modu ve pin toggle yaparak çıkış vermek mantıklı geldi. Aksi halde iki pine mahkum olacaktım. MCU saat frekansı 16MHz ve prescaler kullanmazsam çok büyük rakamlara ulaşacak olan sayıcıda taşma oluşacaktır. Bunun ilave kodlarıyla uğraşmak istemedim. Seçtiğim prescaler çok büyük olursa yüksek frekanslarda çok küçük OCR1A değerleri çıkartacağından gerçek frekansa yaklaşma şansım azalacaktır. Tüm bunları düşünerek prescaler değerini "8" olarak seçtim. CTC modunda timer1 sayıcı (TCNT1) registeri OCR1A ile eşleştiğinde kesme oluşuyor ve TCNT1 sıfırlanıyordu. Bu konu hakkında daha önce paylaşımlarım oldu aynı konuyu tekrar etmeyeceğim. OCR1A değeri ile kesme oluşunca çıkışı pini "1" ve "0" olarak sürekli değişiyor. Bu değişimin aralığı (frekansı) değiştikçe çıkan ses de değişiyor. Aşağıda datasheetten alınma şemada bunu görebilirsiniz.

 

 

 



OC1A (OCna) frekansı için de yukarıdaki formülü kullanıyoruz. Bu formülde "N" prescaler değeri ve Fclk MCU çalışma frekansıdır. Frekans değerleri bir saniyedeki değişime göre belirleniyor, en düşük frekans "1" ve "0" zaman olarak arası en çok olandır. Notaların frekanslarına baktığımızda en düşük frekans bizim için en büyük OCR1A (OCRnA) değeri demektir. Bu nedenle "0" oktav da yer alan notaları baz alarak hesaplama yaptım. "0" dan "8" e kadar oktav değeri büyüdükçe frekans değeri de büyüyor ve OCR1A değeri de düşüyor. Bu değişim frekans olarak bir alt oktavın 2 katı artarken OCR1A değeri olarak aynı oranda azalacaktır. Büyük OCR1A değerini bölerek daha doğru değerler bulmuş oldum. "0" oktav tüm notalar için gerekli değerleri ön tanımlı olarak yüklemek ve diğer oktav değerlerini kod içinde hesaplatmak daha doğru geldi. Örnek olarak "0" oktav "DO" için OCR1A değerini bulalım.

Yukarıdaki formülde OC1A frekansını yerine yazıp OCR1A değerini formülden çekelim. OC1A frekansı bizim çıkış frekansımız yani notanın frekansıdır. Bu durumda formül OCR1A=(Fclk/fOC1A/(2*N))-1 şekline dönüşür. OCR1A=(16000000/nota Hz/(2*8))-1=(16000000/16,35160/16)-1=61155,103 olarak buluruz.

//0 oktav nota ocr değerleri prescaler 8
//datasheet CTC fOCR= f Clk/ (2*N*(1+OCR1A) N prescaler fOCR istenen frekans
// =((16000000/(Nota Hz)/2/8)-1) formülden OCRA değerini çekince bulunur.
#define DO    61155// 0 oktav DO 16,35159 Hz
#define DO_d  57723
#define RE_b  57723
#define RE    54483
#define RE_d  51425
#define MI_b  51425
#define MI    48539
#define FA    45814
#define FA_d  43243
#define SOL_b 43243
#define SOL   40816
#define SOL_d 38525
#define LA_b  38525//0 oktav 25,956 LA/2^(1/12)
#define LA    36363//0 oktav 27,50Hz ---tüm notalar 4 oktav 440Hz e göre hesaplandı 
#define LA_d  34322//0 oktav 29,135 LA*2^(1/12)
#define SI_b  34322
#define SI    32395
#define DO_I  30577

Timer Kesmesi

Bulduğumuz bu değere TCNT1 her ulaştığında yani OCR1A ile her eşleşmede kesme oluşur. Her kesme oluşumunda çıkış için ayarladığımız pin "1" veya "0" olur. Böylece istenen frekansa en yakın çıkışı sağlamış oluruz. Notaların frekansları kadar duyulma süresi yani vuruş zamanı da önemlidir. Aksi halde sadece gürültü olacaktır. Vuruş süresini delay veya benzeri bir şekilde ayarlamak yerine zaten kullanmakta olduğumuz timer ile bir sayaç kullanarak ayarlamak mantıklı olandır. Oktav değiştikçe değişen bir OCR1A değeriyle sabit bir sayaç kullanamayız. Bu nedenle her nota için istediğimiz vuruş süresini hesaplamak gerekir. Bu hesaba daha sonra değineceğim.

Kesme içinde bulduğumuz sayaç değerini kontrol edip sınıra gelince timer1 birimini durdurarak vuruş süresini de ayarlıyoruz. Toggle ile pin durumunu terslerken boşta ya da notalar arası beklemede çıkış vermemesi için her seferinde pin durumunu "1" yapıyorum.  OCR1A tek sayı olduğunda "1" ile başlayıp "1" ile bitirir ve sorun olmazken çift sayı olduğunda bir notadan diğerine geçerken pin durumu "0" olarak kalır ki bu durum çıkışı bozabilir. Pin "0" olduğunda  çıkış alacak şekilde ayarladım. Buzzer "+" ucu +5 volta bağlı, bu nedenle çıkış yokken pin "1" yapıldı. Anlamlı bir müzik için "sus" işaretleri de olmazsa olmazlardan. Sus için bir bayrak tanımlı ve "1" olduğunda toggle işlemi yapılmıyor.

ISR(TIMER1_COMPA_vect){	
	if (kesme_sayac<=nota_sayac){
		if (sus!=1){
			NOTA_PIN_T;//pin toggle
		}
		kesme_sayac++;
	}else{					
		nota_bitti=1;
		kesme_sayac=0;		
		TCCR1B&=~(1<<CS11);//timer1 durdu
		NOTA_PIN_H;//sonraki toggle işlemine high olarak başlar
		sus=0;		
	}	
}

Nota Fonksiyonu

Nota

Notaların güzel bir melodiye dönüşmesi için öncelikle doğru sesi çıkarması gerekir. Bunun için yukarıda gerekli işlemleri yaptım ve çok küçük farklar dışında en yakın frekansları elde ettim. İlk parametre olan notaya tekrar değinmeye gerek yok.

Oktav

Oktav değeri yükseldikçe notanın frekansı artarken OCR1A değeri azalır. Bunun için "0" oktav değeri neyse istenen oktav değeri kadar sağa kaydırarak notanın OCR1A değerini buluyoruz. Örnek olarak 1 oktav değeri bulmak için 0 oktav değerini 2 ye bölmek gerekir. Oktav 1 Nota(OCR1A)=Oktav 0 Nota(OCR1A)/2=Oktav 0 Nota(OCR1A)>>1 olur. Bu şekilde parametre alıp ayarlamanın daha doğru olduğunu düşünüyorum. Diğer türlü bir parçayı farklı değerde çalmak için nota listenizi yenilemeniz gerekir. Bu şekilde sadece nota listenizi girmeniz yeterli olur, tek yapmanız gereken parametreyi değiştirmektir. Bir parça içinde birden fazla oktavda çalınması gereken nota olması durumu için ince hali yani bir üst oktav bilgisini de kod içinde tanımladım. İki ya da daha fazla olması durumunda oktav için bir dizi tanımlanması daha doğru olur. Ben İstiklal marşı için bu şekilde yaptım.

Sonraki adım notaların bir araya gelerek vuruş süresi ve gereken yerlerde de sus ile bir ahenk yakalanmasıdır.

Vuruş Süresi

Notaların vuruş süresini frekans için kullandığımız timer1 ile belirleyeceğim. Bunun için bir sayaç tanımlıyorum ama dediğim gibi sabit bir değer veremiyorum. Bunun nedeni TCNT1 değerinin benim hangi oktav da olduğuma bakmaksızın bir mikro saniyede 2 kere artmasıdır. 4 Oktav LA için OCR1A değeri 2273, 5 Oktav LA için 1136 olur. Biz 4 oktav da kullandığımız sayaç değerinin iki katını 5 oktav da kullanırsak ancak aynı vuruş süresini buluruz. Bir saniye olarak baz aldığım ikilik bir LA için hesap yapalım. 4 Oktav La notasını elde etmek için TCNT1 2273 olmalı, OCR1A ile eşleşmeli ve kesme oluşmalıdır. Bunun için gereken 2273 saat darbesi 2273/2=1136,33 µs dir. (Prescaler 8 ve 16000000/8=2000000 saniye=2 µs) Bu hesaba göre OCR1A değerinin yarısı bize geçen zamanı verir. Her 1136µs bir kesme oluşur ve sayaç değeri bir artar. Bunu ms cinsine çevirelim 1136,33/1000=1,13633ms buluruz.

Kesme için gereken süreyi yani sayaç değerinin 1 artışındaki süreyi ms cinsinden bulduk. Bir milisaniye için sayaç değerini bulmamız gerekiyor. Bunun için de 1/1,13633=0,880 olur. Bu değer 4 oktav la için oluşan kesmelerle artan sayacın 1ms içindeki değeridir. Artık bir saniye=1000ms için sayaç değerini bulmak kolay 1000*0,880=880dir. Sayaç eğer 880 olursa 1 saniye geçmiş demektir. Aynı hesabı 5 oktav için yaparsak sayaç için: 1136/2=568µs=568/1000=0,568 ms sayacın bir artması için geçen süre, 1/0,568=1,760 bir ms deki sayaç değeridir. Bir saniye için sayaç=1,760*1000= 1760 buluruz.

Karışık olarak yaptığımız bu işlemleri bir formül haline getirelim. Sayaç= istenen süre(vuruş)*((2*1000)/OCR1A)=vuruş*2000/OCR1A olur. Bu üç parametre sonrası notanın sonuna kadar çalınabilmesi için zoraki bir döngü kullanmak gerekiyor. Bunun için de bir bayrak tanımladım.

Aşağıda kod içinde yeterli açıklamanın mevcut olduğunu düşünüyorum.

Not: Pasif buzzer kullanılması gerekiyor.


/*
* nota_timer_1.c
*
* Created: 17.02.2022 14:32:47
* Author : haluk
*/


#define F_CPU 16000000ul
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>



#define NOTA_PORT	PORTD
#define NOTA_DIR	DDRD
#define NOTA_PIN	PORTD5

#define NOTA_OUT	NOTA_DIR|=(1<<NOTA_PIN);
#define NOTA_PIN_H	NOTA_PORT|=(1<<NOTA_PIN);
#define NOTA_PIN_L	NOTA_PORT&=~(1<<NOTA_PIN);
#define NOTA_PIN_T	NOTA_PORT^=(1<<NOTA_PIN);

//0 oktav nota ocr değerleri prescaler 8
//datasheet CTC fOCR= f Clk/ (2*N*(1+OCR1A) N prescaler fOCR istenen frekans
#define DO    61155// =((16000000/(Nota Hz)/2/8)-1) formülden OCRA değerini çekince bulunur. 
#define DO_d  57723
#define RE_b  57723
#define RE	  54483
#define RE_d  51425
#define MI_b  51425
#define MI    48539
#define FA    45814
#define FA_d  43243
#define SOL_b 43243
#define SOL   40816
#define SOL_d 38525
#define LA_b  38525//0 oktav 25,956 LA/2^(1/12)
#define LA    36363//0 oktav 27,50Hz ---tüm notalar 4 oktav 440Hz e göre hesaplandı
#define LA_d  34322//0 oktav 29,135 LA*2^(1/12)
#define SI_b  34322
#define SI    32395
#define DO_I  30577

//1 oktav nota ocr değerleri prescaler 8
//her oktav değeri bir öncekinin yarısı
//her parçada oktav dizisi oluşturmamak için yazıldı çok gerekli değil.
// bkz. İstiklal marşı
#define I_DO_d  (DO_d>>1)
#define I_RE_b  (RE_b>>1)
#define I_RE	(RE>>1)
#define I_RE_d  (RE_d>>1)
#define I_MI_b  (MI_b>>1)
#define I_MI    (MI>>1)
#define I_FA    (FA>>1)
#define I_FA_d  (FA_d>>1)
#define I_SOL_b (SOL_b>>1)
#define I_SOL   (SOL>>1)
#define I_SOL_d (SOL_d>>1)
#define I_LA_b  (LA_b>>1)
#define I_LA    (LA>>1)
#define I_LA_d  (LA_d>>1)
#define I_SI_b  (SI_b>>1)
#define I_SI    (SI>>1)
#define I_DO_I  (DO_I>>1)

volatile uint8_t sus=0,nota_bitti=0;
volatile uint16_t onceki_nota=0;
volatile uint32_t nota_sayac=0, kesme_sayac=0;

void timer1_init();
void nota_cal(uint16_t nota,uint8_t oktav, uint32_t vurus);

const uint16_t YASASIN[]={//yaşasın okulumz
	DO,DO,SOL,SOL,LA,LA,SOL,0,
	FA,FA,MI,MI,RE,RE,DO,0,
	SOL,SOL,FA,FA,MI,MI,RE,0,
SOL,SOL,FA,FA,MI,MI,RE,0};
const PROGMEM uint16_t  GEZSEN[115] ={//gezsen anadoluyu
	DO,FA,MI,FA,MI,FA,SOL,RE,0,
	DO,FA,MI,FA,MI,DO,RE,0,
	DO,DO,FA,FA,MI,FA,SOL,RE,0,
	DO,FA,MI,FA,MI,DO,RE,0,
	SOL,LA,FA,SOL,LA,SOL,LA,FA,SOL,0,
	FA,SOL,MI,FA,SOL,FA,SOL,MI,FA,0,
	FA,MI,RE,DO,MI,FA,SOL,RE,0,
	DO,FA,MI,FA,MI,DO,RE,0,
	DO_I,DO_I,SI,DO_I,SI,DO_I,LA,0,
	SOL,LA,SOL,DO_I,LA,SI_b,SOL,0,
	SOL,FA,SOL,LA,0,
	FA,SOL,MI,MI,FA,RE,0,
	DO,FA,MI,FA,MI,DO,RE,0,
	DO,FA,MI,FA,MI,DO,RE,0
};
const PROGMEM uint16_t GEZSEN_vur[115] ={
	500,125,125,250,125,125,250,500,125,
	250,250,125,125,125,125,1000,125,
	250,250,250,250,125,125,250,500,125,
	250,250,125,125,125,125,1000,125,
	250,250,250,250,125,125,125,125,500,125,
	250,250,250,250,125,125,125,125,500,125,
	125,375,250,250,125,125,250,500,125,
	250,250,125,125,125,125,1000,125,
	250,250,125,375,375,125,500,125,
	250,250,250,250,375,125,500,125,
	1500,125,125,250,125,
	375,125,500,375,125,500,125,
	250,250,125,125,125,125,1000,125,
	500,500,250,250,250,250,1500,750
};
const PROGMEM uint16_t IZMIR[124]={//izmir marşı
	RE,LA,LA,LA,LA,LA,LA,SI_b,LA,SOL,SI_b,LA,0,
	RE,LA,LA,LA,LA,LA,LA,SI_b,LA,SOL,SI_b,LA,0,
	LA,I_RE,DO_I,SI_b,LA,SOL,LA,SOL,FA,MI,FA,0,
	FA,LA,SOL,FA,MI,RE,FA,MI,RE,DO_d,RE,0,
	RE,LA,LA,LA,LA,LA,LA,SI_b,LA,SOL,SI_b,LA,0,
	RE,LA,LA,LA,LA,LA,LA,SI_b,LA,SOL,SI_b,LA,0,
	LA,I_RE,DO_I,SI_b,LA,SOL,LA,SOL,FA,MI,FA,0,
	FA,LA,SOL,FA,MI,RE,FA,MI,RE,DO_d,RE,0,
	LA,I_RE,DO_I,SI_b,LA,SOL,LA,SOL,FA,MI,FA,0,
	FA,LA,SOL,FA,MI,RE,FA,MI,RE,DO_d,RE,0,
	
	
};
const PROGMEM uint16_t IZMIR_vur[124]={
	500,250,250,250,250,250,250,250,250,250,250,750,250,
	500,250,250,250,250,250,250,250,250,250,250,750,250,
	500,500,250,250,250,250,250,250,250,250,750,250,
	500,500,250,250,250,250,250,250,250,250,750,250,
	500,250,250,250,250,250,250,250,250,250,250,750,250,
	500,250,250,250,250,250,250,250,250,250,250,750,250,
	500,500,250,250,250,250,250,250,250,250,750,250,
	500,500,250,250,250,250,250,250,250,250,750,250,
	500,500,250,250,250,250,250,250,250,250,750,250,
	500,500,250,250,250,250,250,250,250,250,750,250,
};
const PROGMEM uint16_t CAVBELLA[67]={
	MI,LA,SI,DO_I,LA,0,
	MI,LA,SI,DO_I,LA,0,
	MI,LA,SI,DO_I,SI,LA,DO_I,//doI
	SI,LA,I_MI,I_MI,I_MI,0,
	I_MI,I_RE,I_MI,I_FA,I_FA,0,
	I_FA,I_MI,I_RE,I_FA,I_MI,0,
	I_MI,I_RE,DO_I,SI,I_MI,DO_I,//do
	I_RE,I_MI,0,
	LA,
	I_RE,I_MI,I_FA,I_FA,0,
	I_FA,I_MI,I_RE,I_FA,I_MI,0,
	I_MI,I_RE,DO_I,SI,I_MI,DO_I,//do
	SI,LA,0,
};
const PROGMEM uint16_t CAVBELLA_vur[67]={
	250,250,250,250,1000,125,
	250,250,250,250,1000,125,
	250,250,250,500,250,250,500,//doI
	250,250,500,500,250,125,
	250,250,250,250,1000,125,
	250,250,250,250,1000,125,
	250,250,250,500,500,500,//do
	500,1000,250,
	250,
	250,250,250,1000,125,
	250,250,250,250,1000,125,
	250,250,250,500,500,500,//do
	500,1000,750,
};
const PROGMEM uint16_t babyshark_nota[170]={
	RE,MI_b,0,
	RE,MI_b,0,
	RE,MI_b,0,
	RE,MI_b,0,
	RE,MI_b,0,
	RE,MI_b,0,
	RE,MI_b,0,
	RE,MI_b,0,
	SOL,FA,SOL,LA_b,SOL,FA,RE,FA,
	SOL,FA,SOL,LA_b,SOL,FA,RE,FA,
	SOL,FA,SOL,LA_b,SOL,FA,MI,SOL,
	RE,0,
	I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_FA_d,0,
	I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_FA_d,0,
	I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_FA_d,0,
	I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_RE,I_MI,
	I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_SOL,I_FA_d,0,
};
const PROGMEM uint16_t babyshark_vur[170]={
	1000,125,875,
	1000,125,875,
	500,125,375,
	500,125,375,
	250,125,125,
	250,125,125,
	250,125,125,
	250,125,125,
	250,250,250,250,250,250,250,250,
	250,250,250,250,250,250,250,250,
	250,250,250,250,250,250,250,250,
	250,750,
	500,500,
	250,250,250,125,250,125,250,250,
	250,250,250,125,250,125,250,250,250,
	250,250,250,125,250,125,250,250,250,250,750,
	500,500,
	250,250,250,125,250,125,250,250,
	250,250,250,125,250,125,250,250,250,
	250,250,250,125,250,125,250,250,250,250,750,
	500,500,
	250,250,250,125,250,125,250,250,
	250,250,250,125,250,125,250,250,250,
	250,250,250,125,250,125,250,250,250,250,750,
	500,500,
	250,250,250,125,250,125,250,250,
	250,250,250,125,250,125,250,250,250,
	250,250,250,125,250,125,250,250,250,250,1750,
};
const PROGMEM uint16_t starwars[73]={
	SOL,SOL,SOL,MI_b,SI_b,
	SOL,MI_b,SI_b,SOL,0,
	I_RE,I_RE,I_RE,I_MI_b,SI_b,
	SOL_b,MI_b,SI_b,SOL,
	I_SOL,SOL,SOL,I_SOL,I_SOL_b,I_FA,
	I_MI,I_RE_d,I_MI,0,SOL_d,I_DO_d,DO_I,SI,
	SI_b,LA,SI_b,0,MI_b,SOL_b,MI_b,SOL_b,
	SI_b,SOL,SI_b,RE,0,
	I_SOL,SOL,SOL,I_SOL,I_SOL_b,I_FA,
	I_MI,I_RE_d,I_MI,0,SOL_d,I_DO_d,DO_I,SI,
	SI_b,LA,SI_b,0,MI_b,SOL,MI_b,SI_b,
	SOL,MI_b,SI_b,SOL,0,
	
};
const PROGMEM uint16_t starwars_vur[73]={
	500,500,500,375,125,
	500,375,125,1000,125,
	500,500,500,250,125,
	500,375,125,1000,
	500,375,125,500,375,125,
	125,125,250,250,250,500,375,125,
	125,125,250,250,250,500,375,125,
	500,375,125,1000,125,
	500,375,125,500,375,125,
	125,125,250,250,250,500,375,125,
	125,125,500,250,250,500,375,125,
	500,375,125,1000,750,
	
};
const PROGMEM uint16_t  cember[43]={//çemberimde gül oya
	MI,MI,MI,SOL,SOL,FA,MI,
	DO_I,DO_I,DO_I,DO_I,SI,LA,SI,LA,
	SOL,0,
	SOL,SOL,DO_I,DO_I,SI,LA,DO_I,DO_I,
	SI,SI,LA,SOL,SI,SI,SI,LA,SOL,
	LA,LA,DO_I,SI,LA,SOL,FA,
	MI,0,
};
const PROGMEM uint16_t  cember_vur[43]={
	250,250,250,250,250,250,750,
	375,125,250,250,250,250,500,250,
	1000,125,
	250,250,250,250,250,250,250,500,
	250,250,250,250,250,250,250,250,250,
	500,250,250,250,250,500,250,
	1000,750,
};
const PROGMEM uint16_t  panther[93]={
	RE_d,
	MI,0,FA_d,SOL,0,RE_d,
	MI,FA_d,SOL,DO_I,SI,MI,SOL,SI,
	SI_b,/*SI_b*/LA,SOL,MI,RE,MI,
	/*MI*/0,0,RE_d,
	MI,0,FA_d,SOL,0,RE_d,
	MI,FA_d,SOL,DO_I,SI,SOL,SI,I_MI,
	I_MI_b,/*I_MI_b*/0,RE_d,
	MI,0,FA_d,SOL,0,RE_d,
	MI,FA_d,SOL,DO_I,SI,MI,SOL,SI,
	SI_b,/*SI_b*/LA,SOL,MI,RE,MI
	/*MI*/,0,
	0,I_MI,I_RE,SI,LA,SOL,MI,
	SI_b,LA,SI_b,LA,SI_b,LA,SI_b,LA,
	SOL,MI,RE,MI,MI,/*MI*/
	SOL,MI,RE,MI,MI,/*MI*/
	SOL,MI,RE,MI,MI,/*MI*//*MI*/0,
	
};
const PROGMEM uint16_t  panther_vur[93]={
	125,
	500,375,125,500,375,125,
	375,125,375,125,375,125,375,125,
	1250,/*SI_b*/250,250,250,250,1250,
	/*MI*/500,375,125,
	500,375,125,500,375,125,
	375,125,375,125,375,125,375,125,
	3500,/*I_MI_b*/375,125,
	500,375,125,500,375,125,
	375,125,375,125,375,125,375,125,
	1250,/*SI_b*/250,250,250,250,1750
	/*MI*/,500,
	500,375,125,375,125,375,125,
	125,375,125,375,125,375,125,375,
	250,250,250,250,1250,/*MI*/
	250,250,250,250,1250,/*MI*/
	250,250,250,250,3250,/*MI*//*MI*/750,
	
};
const PROGMEM uint16_t  alibaba[38]={
	DO,
	FA,FA,FA,LA,
	SOL,FA,MI,FA,SOL,
	SOL,SOL,SOL,SI_b,
	LA,SOL,FA,SOL,LA,
	DO_I,0,DO_I,0,
	I_RE,DO_I,SI_b,LA,SI_b,
	SI_b,LA,SOL,FA,SOL,DO_I,
	LA,SOL,FA,0,
};
const PROGMEM uint16_t  alibaba_vur[38]={
	250,
	500,500,500,500,
	250,250,250,250,1000,
	500,500,500,500,
	250,250,250,250,1000,
	500,500,500,500,
	250,250,250,250,1000,
	250,250,250,250,500,500,
	500,500,750,750,
};
const PROGMEM uint16_t  benim[31]={//benim annem
	DO,RE,
	MI,MI,MI,FA,
	MI,RE,RE,MI,
	FA,LA,SOL,FA,
	MI,SOL,FA,
	RE,RE,MI,FA,
	LA,SOL,DO_I,LA,
	SOL,MI,SOL,FA,
	MI,0
};
const PROGMEM uint16_t  benim_vur[31]={//benim annem
	250,250,
	500,500,250,250,
	500,500,250,250,
	750,250,250,250,
	1000,250,250,
	500,500,250,250,
	500,500,250,250,
	500,500,250,250,
	1000,750
};
const PROGMEM uint16_t  istiklal[62]={//İstiklal marşı
	SI,
	MI,FA_d,SOL,RE_d,FA_d,
	MI,MI,
	LA,SI,DO,SI,SOL_d,SI,
	LA,SI,LA_d,SI,
	FA_d,FA_d/*FA_d*/,LA,SOL,RE_d,
	MI,FA_d,SOL,LA,SI,DO,RE,MI,
	RE,0,RE,DO_d,RE,SI,LA,
	SOL,SI,LA_d,SI,
	FA_d,SI,SI,LA,SOL,FA_d,SOL,
	MI,MI/*MI*/,RE,DO,SI,
	LA,SOL,FA_d,MI,SI,SI,
	MI,0,
};
const PROGMEM uint16_t  istiklal_oktav[62]={//İstiklal marşı
	-1,
	0,0,0,0,0,
	0,0,
	0,0,1,0,0,0,
	0,0,0,0,
	0,0/*FA_d*/,0,0,0,
	0,0,0,0,0,1,1,1,
	1,0,0,0,0,0,0,
	0,-1,-1,-1,
	0,-1,0,0,0,0,0,
	0,1/*MI*/,1,1,0,
	0,0,0,0,0,-1,
	0,0,
};
const PROGMEM uint16_t  istiklal_vur[62]={//İstiklal marşı
	500,
	500,500,500,375,125,
	1500,500,
	500,500,375,125,375,125,
	1500,250,250,250,
	500,875/*FA_d*/,125,375,125,
	375,125,375,125,375,125,375,125,
	500,125,250,250,250,500,500,
	1500,250,250,250,
	500,500,375,125,250,250,250,
	500,875/*MI*/,125,375,125,
	375,125,375,125,500,500,
	1500,1000,
};
const PROGMEM uint16_t  erikdali[89]={
	0,SI,SI,SI,SI,SI,DO_I,SI,LA,SI,LA,
	0,SI,SI,SI,SI,SI,DO_I,SI,LA,SI,LA,
	LA,LA,0,LA,SI,SOL,FA,MI,SOL,
	LA,LA,0,LA,SI,SOL,FA,MI,
	MI,SOL,LA,SOL,LA,SOL,LA,SI,SOL,FA,MI,SOL,
	LA,SOL,LA,SOL,LA,SOL,LA,SI,SOL,SOL,FA,MI,0,
	MI,SOL,LA,SOL,LA,SOL,LA,SI,SOL,FA,MI,SOL,
	LA,SOL,LA,SOL,LA,SOL,LA,SI,SOL,SOL,FA,MI,0
};
const PROGMEM uint16_t  erikdali_vur[89]={
	250,125,125,250,250,125,125,125,125,250,250,
	250,125,125,250,250,125,125,125,125,250,250,
	250,250,250,125,125,375,125,250,250,
	250,250,250,125,125,375,125,500,
	125,125,125,125,125,125,125,125,375,125,250,250,
	125,125,125,125,125,125,125,125,125,375,125,500,125,
	125,125,125,125,125,125,125,125,375,125,250,250,
	125,125,125,125,125,125,125,125,125,375,125,500,750
};
const PROGMEM uint16_t  dugme[54]={//kol düğmeleri
	MI,FA_d,SOL,
	SI,
	0,I_RE,DO_I,
	SI,DO_I,SI,LA,SI/*SI*/,
	0,MI,FA_d,SOL,
	SI,
	0,I_MI,I_RE,DO_I,
	SI,DO_I,SI,LA,SI/*SI*/,
	0,SOL,LA,SI,
	DO_I,SI,
	LA,DO_I,SI,LA,
	SI/*SI*/,LA,
	SOL,SOL,DO_I,SI,
	LA/*LA*/,SOL,
	LA,LA,I_RE,DO_I,
	SI,DO_I,SI,LA,SI/*SI*/,0
	
};
const PROGMEM uint16_t  dugme_vur[54]={//kol düğmeleri
	250,250,250,
	1500,
	750,500,250,
	62,62,62,62,1250/*SI*/,
	750,250,250,250,
	1500,
	750,250,250,250,
	62,62,62,62,1250/*SI*/,
	750,250,250,250,
	1250,250,
	750,250,250,250,
	1250/*SI*/,250,
	750,250,250,250,
	1250/*LA*/,250,
	750,250,250,250,
	62,62,62,62,1250/*SI*/,125
	
};
const PROGMEM uint16_t  bluey[88]={
	LA,SOL,FA,MI,0,DO,RE/*RE*/,
	FA,0,DO,RE,DO,0,
	LA,SOL,FA,MI,0,DO,RE/*RE*/,
	FA,0,
	LA,SOL,FA,MI,0,DO,RE/*RE*/,
	FA,0,DO,RE,DO,0,
	LA,SOL,FA,MI,0,DO,RE/*RE*/,
	FA,0,
	I_LA,I_SOL,I_FA,I_MI,0,DO_I,I_RE/*I_RE*/,
	I_FA,0,DO_I,I_RE,DO_I,0,
	I_LA,I_SOL,I_FA,I_MI,0,DO_I,I_RE/*I_RE*/,
	I_FA,0,
	I_LA,I_SOL,I_FA,I_MI,0,DO_I,I_RE/*I_RE*/,
	I_FA,0,DO_I,I_RE,DO_I,0,
	I_LA,I_SOL,I_FA,I_MI,0,DO_I,I_RE/*I_RE*/,
	I_FA,0,
	
};
const PROGMEM uint16_t  bluey_vur[88]={
	250,250,250,250,250,500,750/*RE*/,
	250,250,250,250,250,250,
	250,250,250,250,250,500,750/*RE*/,
	250,1250,
	250,250,250,250,250,500,750/*RE*/,
	250,250,250,250,250,250,
	250,250,250,250,250,500,750/*RE*/,
	250,1250,
	250,250,250,250,250,500,750/*I_RE*/,
	250,250,250,250,250,250,
	250,250,250,250,250,500,750/*I_RE*/,
	250,1250,
	250,250,250,250,250,500,750/*I_RE*/,
	250,250,250,250,250,250,
	250,250,250,250,250,500,750/*I_RE*/,
	250,1250,
	
};

ISR(TIMER1_COMPA_vect){
	if (kesme_sayac<=nota_sayac){
			if (sus!=1){
				NOTA_PIN_T;//pin toggle
			}
			kesme_sayac++;
		}else{
			nota_bitti=1;
			kesme_sayac=0;
			TCCR1B&=~(1<<CS11);//timer1 durdu
			NOTA_PIN_H;//sonraki toggle işlemine high olarak başlar
			sus=0;
	}
}
int main(void){
	NOTA_OUT;
	NOTA_PIN_H;
	timer1_init();
	nota_cal(DO,8,1);
	_delay_ms(500);
	while (1){
		for (uint8_t i=0;i<62;i++){
		nota_cal(pgm_read_word_near(istiklal+i),pgm_read_word_near(istiklal_oktav+i)+4,pgm_read_word_near(istiklal_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<124;i++){
		nota_cal(pgm_read_word_near(IZMIR+i),4,pgm_read_word_near(IZMIR_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<54;i++){
		nota_cal(pgm_read_word_near(dugme+i),3,pgm_read_word_near(dugme_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<43;i++){
		nota_cal(pgm_read_word_near(cember+i),4,pgm_read_word_near(cember_vur+i));
		_delay_ms(10);
		}		
		for (uint8_t i=0;i<115;i++){
		nota_cal(pgm_read_word_near(GEZSEN+i),4,pgm_read_word_near(GEZSEN_vur+i));
		_delay_ms(30);
		}
		for (uint8_t i=0;i<67;i++){
		nota_cal(pgm_read_word_near(CAVBELLA+i),4,pgm_read_word_near(CAVBELLA_vur+i));
		_delay_ms(30);
		}
		for (uint8_t i=0;i<89;i++){
		nota_cal(pgm_read_word_near(erikdali+i),4,pgm_read_word_near(erikdali_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<73;i++){
		nota_cal(pgm_read_word_near(starwars+i),3,pgm_read_word_near(starwars_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<93;i++){
		nota_cal(pgm_read_word_near(panther+i),4,pgm_read_word_near(panther_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<31;i++){
		nota_cal(pgm_read_word_near(benim+i),4,pgm_read_word_near(benim_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<32;i++){
		nota_cal(YASASIN[i],5,500);
		_delay_ms(10);
		}
		for (uint8_t i=0;i<38;i++){
		nota_cal(pgm_read_word_near(alibaba+i),4,pgm_read_word_near(alibaba_vur+i));
		_delay_ms(10);
		}
		for (uint8_t i=0;i<170;i++){
			nota_cal(pgm_read_word_near(babyshark_nota+i),4,pgm_read_word_near(babyshark_vur+i));
			_delay_ms(10);
		}
		for (uint8_t i=0;i<88;i++){
			nota_cal(pgm_read_word_near(bluey+i),4,pgm_read_word_near(bluey_vur+i));
			_delay_ms(10);
		}		
	}
}
void timer1_init(){
	cli();
	TCCR1B|=(1<<WGM12)|(1<<CS11);//ctc, pre 8
	TIMSK1|=(1<<OCIE1A);
	sei();
}
void nota_cal(uint16_t nota,uint8_t oktav, uint32_t vurus){	
	nota>>=oktav;//oktava göre ocr belirleniyor	
	if (nota!=0){
//Sayaç= istenen süre(vuruş)*((2*1000)/OCR1A) ms olarak istenen sürede oluşması gereken kesme sayısını verir
		nota_sayac=(vurus*2000)/nota;
		onceki_nota=nota;// önceki notanın değeri
		OCR1A=nota;//kesme aralığı
	}else{
		sus=1;
		nota_sayac=(vurus*2000)/onceki_nota;// es süresi
		OCR1A=onceki_nota;	//kesme aralığı önceki notaya göre aldım
	}
	TCCR1B|=(1<<CS11);//timer açıldı	
	while (nota_bitti==0);//nota bitene kadar bekler
	nota_bitti=0;	
}

 

 

 

 

 

AVR- PID Sıcaklık Kontrolü


Çok ilgimi çeken bir konu olmuştur. Ne yazık ki uygulamaya dönük pek kaynak bulamadım. Genelde yabancı dilde kaynaklar veya benim gibi amatörün anlamakta zorlandığı yazılar var. Bu konuda en basit anlatım ne yazık ki artık yayında olmayan, bu nedenle de bağlantı veremediğim bir yerdeydi. En zor olan kısım başta PID algoritmasını kurmak ve bunu yazılıma aktarmak oldu. Önceki bir yazımda (P) oransal kontrolü denemiştim. Hatta hazır kodlarla PID denemesi de yaptım ama o zaman beceremedim. Bilmediğim bir konuda ezbere bir şey denemekle doğru sonuca ulaşmayı beklemek büyük bir hataydı. Şu anda da biliyorum diyemem. En azından pratik olarak çözüm getirebilecek PID kontrolü kullanabilecek kadar anladım diyebilirim. PID konusunda sorun olan diğer kısım da katsayıların bulunması oldu. Bunun gibi bazı kaynaklar mevcut. Ben bu şeklide bir sonuca varamadım. Bu konuya daha sonra değineceğim.

PID

Birçok kaynağı okumaya kavramaya çalıştım ve anladığımı basitçe aktarmaya çalışayım. PID bir sistemde belirlenen hedef değere ulaşmaya çalışan çeşitli yöntemlerinden birisidir. PID oransal, integral ve türevin İngilizce kısaltmasıdır. Hedef bir değer ve anlık olarak mevcut durum değeri vardır. Hedefle bu anlık değer arasındaki fark oluşan hatadır. Bu hata değeri kullanarak çeşitli hesaplamalarla çıkış gücünün belirlenmesi ve bunun bir döngü halinde yapılması PID kontroldür. Sürekli olarak anlık kontrol ve buna göre çıkış gücü belirlenmektedir. Çıkışı belirleyen üç çeşit hesaplama yapılmaktadır. Bunlar oransal, integral ve türev hesaplamalarıdır. Günlük yaşantımızda farkında olmadan refleks olarak yaptığımız birçok şey örnek olabilir. Araba ile hızımızı kontrol etmemizden tutunda bir şişeye musluktan su doldurmaya kadar,  beynimizin kullandığı bir sistem. Şişeyi doldururken önce sonuna kadar açıp dolmaya yakın yavaş yavaş musluğu kapatmamız, taşacak sanıp biraz daha kapatıp ama dolmadığı için tekrar açmamız bir örnek olabilir. Öncelikle oransal kısmını ele alalım.

Oransal (P)

Bir ortamı ısıtmak istiyorum. Limit değer olarak 30°C belirledim. Ortam 21°C ve farkı kapatmak için ısıtıcıyı çalıştırıyorum. İstenen sıcaklığa gelince ısıtıcıyı kapatıyorum. Bu şekilde aç-kapa yaptığımızda asla istenen değerde sabit tutamayız. Sıcaklık istenen değere geldiğinde kapatsak da bir süre ısıtıcı sıcaklığı artırmaya devam edecektir. Limit altına düşünce de ısıtıcı çalışmaya başlayıp ısıtana kadar sıcaklık düşmeye devam edecektir. Bu nedenle limite yaklaştıkça ısıtıcının verdiği enerjiyi azaltmak ve düştükçe artırmak daha mantıklı olacaktır.

Bunun için ısıtıcı kaynağa verdiğim çıkışı dimmer ile ayarlıyorum. Dimmer ayarı için sıfır geçişi sonrası bekleme süresini değiştirerek ayarlama yapıyorduk. Bu bekleme süresini de limitle anlık değer arasındaki farka göre belirliyoruz. Örneğin 30 °C ayarlı ortamın sıcaklığı 25°C ise aradaki 5°C fark sistemin hatasıdır. Bu farkın kapanması için ısıtıcı belli bir oranda çalıştırılmalıdır. Sistem çalışmaya devam ediyor ve belli aralıklarla sıcaklığı okuyoruz. Aradaki fark 1°C ye geldiğinde ısıtıcı yine çalışmalı ama bu daha düşük bir voltajda olmalıdır. 5°C de dimmer değeri "0" yani tam güçle çalışırken 1°C olduğunda dimmer değeri 500 olmalı, 0,1°C için 2300 olmalıdır. Burada dimmer değerinin artması kafa karıştırıcı duruyor. Tekrar hatırlatayım bu değerler bekleme süresidir ve arttıkça çıkış voltajı azalır. Bekleme  yani dimmer değeri olarak belirttiğim değerler 0 ile 2500 arasındadır. 2500 değeri 10ms ye karşılık gelir.

Sıcaklık farkı (hata) birer birer değişirken benim dimmer değerini 0 ile 2500 arası değerlere getirmem gerekiyor. Hatanın değişim oranına göre dimmer değerini belirlemek için bir katsayıya ihtiyaç var. Bu katsayı, PID kısaltmasının P yani oransal değerini bulacağımız kp  katsayısıdır. Böylece P=hata x kp şeklinde P değerini buluruz. Sıcaklık farkı azaldıkça dimmer değerimiz artıyor. Bunun için de Dimmer=2500-P şeklinde yazmamız yeterli olur. Fark 1°C olduğunda P= 1 x2000 ile P=2000 ve Dimmer= 2500-2000=500 olur.

Kısaca tekrar edeceğim. İstenen değer ile anlık değer arasındaki fark hatadır. Bu hatanın büyüklüğüne göre çıkışın ayarlanmasına oransal kontrol denir. Burada çıkış dimmer değeri ile kontrol ettiğimiz devrenin voltajıdır. Fark azaldıkça dimmer değeri artar çıkış voltajı da azalır. Fark artarsa dimmer değeri azalır çıkış voltajı artar.

Oransal Uygulama

Oluşturduğum test ortamı basit bir plastik kutu, bir ampul, bir  PC fanı ve sıcaklık sensöründen oluşuyor. Ampulü dimmer ile kontrol ediyorum. Aydınlık seviyesi artıkça yaydığı ısı da artıyor. Kutu içindeki sıcaklığı AHT10 ile okuyorum ve bu şekilde dimmer seviyesini belirliyorum. (AHT10 ve AC dimmer isimli yazılarıma göz atmanız buradaki anlatımı daha anlaşılır kılacaktır.) Ortam sıcaklığı 21°C ve kutuda izolasyon olmadığı için çok fazla kayıp var bu nedenle en fazla 44 °C oldu. Bir saniye aralıklarla yaptığım ölçümlerde sıcaklık artışı başta 0,8 °C iken Δt arttıkça bu sıfıra düştü. Yine en üst seviyeden aşağı doğru 0,25 °C bir kayıp ile başladı. Bu rakamları neden verdiğimi daha sonra açıklayacağım.

Öncelikle dimmer, P, kp ve Fark için değişkenlere ihtiyacımız var.  Bu değişkenleri Float cinsinden belirleyeceğiz. Böylece virgülden sonrası içinde hesap yapabileceğiz ve daha hassas bir çıkış alacağız. Aşağıda tüm fonksiyonları eklemedim en altta toplu olarak paylaşacağım. Sonsuz döngü  içinde sıcaklık değerini okuyup set değerinden çıkartıyoruz. Bu işlem sonucunda bulduğumuz değer  hatadır. Bulunan hatayı Kp ile çarparak P_dim değerini buluyoruz. Bu değer oransal kontrol değeridir. Dimmer beklemesi için gereken süreyi 2500 den çıkartıp buluyoruz.

uint16_t set_deger=0;
float hata=0;
float kp=2000.0;
volatile int32_t dimmer=2500;
volatile float p_dim=0.0;

ISR(INT0_vect){			
		dimmer=2500-p_dim;
		if (dimmer<=0){//dimmer değeri sınırlanıyor
			dimmer=0;
		}
		if (dimmer>=2500){
			dimmer=2500;
		}
		TCCR1B|=(1<<WGM12)|(1<<CS10)|(1<<CS11);//timer çalıştı
		OCR1A=dimmer;//timer kesmesi için gereken süre		
}
int main(void){
	aht10_init();	
	while(1){
		
		hata=set_deger-aht10_read();//sıcaklık okundu set değer farkı hata
		p_dim=kp*hata;				
		
	}
}

Bu şekilde kontrol ettiğimizde alacağımız sonuçlar aşağıdaki gibi olacaktır. Kp katsayıları sırasıyla 1500,3000 ve 6000 olarak ayarlıdır.





Grafiklerde görüldüğü gibi Kp büyüdükçe hedefe yaklaşıyor ama tam hedef değeri yakalayamıyor. Bu grafikler için bilerek böyle büyük değerler seçtim ve mümkün olduğunca eşit şartlarda başlattım. PID katsayıları hesaplama yöntemlerinden biri olan ve uygulayabileceğim bir yöntem gibi gelen  Ziegler-Nichols yönteminde ben yanlış anlamadıysam Kp değeri ile bir salınıma girmesi gerekiyor. Ben ne yazık ki salınımı bu düzenekte göremedim. Belki ısıtıcı olarak kullandığım ampul, izolasyonu olmayan kutu belki de bunu tespit etmek için gerekli ekipmana sahip değilim. Kp için 50000 gibi bir değer yazınca çok küçük bir miktar salınım yakalamış olsam da bu salınımın periyodunu bulmam çok zor. Yaklaşık bir değer bulup bununla Ki ve Kd hesaplaması yaptığımda çıkan değerlerin uygun değerler olmadığını söyleyebilirim.

Oransal kısımda hedef ile anlık değer arasındaki hataya göre çıkış değerimizi ayarlıyoruz, hata azaldıkça çıkış azalıyor. Bu nedenle hedef değer altında kalıyor istenen değere ulaşamıyoruz. Biz istenen değere ulaşamıyoruz ama hata asla sıfırlanmıyor. Küçük bir değer olsa da devam ediyor. Üstteki grafik üstünde konuşursak. Hedef değer 40 °C seçili ilk başta 36 °C olan sıcaklık artmış 39,5-39,6 °C arasında bir değerde kalmıştır. Bu durumda hata 0,5-0,4 arasında olmuştur. Buna göre bir hesap yapalım. P=Kp x hata =1500x0,5=750 olur. P 750 ise dimmer bekleme değeri 2500-750=1750 olur. Bu değer ancak bizim ısı kaybımızı karşılamıştır. Daha düşük bir dimmer değeri olmalı ki çıkış gücü artsın ve ortama verilen ısı artsın, böylece hedef değere ulaşabilelim. Sistemin sıcaklığı arttıkça P azalacak dimmer artacaktır yani sadece oransal çıkış değeriyle hedefe ulaşamayız.

İntegral (I)

Bu deney kutusunda ve gerçek bir sistemde ısı kaybı olacaktır. Dış ortamla iç hacim arasında sıcaklık farkı arttıkça bu kayıp artacaktır. Hedef altında kaldığımızda oluşan hata her ölçüm anında devam eder. Başta söylediğim gibi PID kontrolde sürekli sabit aralıkta ölçüm ve buna göre çıkış hesaplaması yapılır. Bu ölçümlerde belirlenen geçmiş hataların toplamı yani "I" dimmer değerinin düşmesini sağlayacak olan çıkışı verir. Sistemin kaybını bulur ve buna göre çıkışı düzenler. (Aslında integral hata-zaman grafiğinde oluşan eğrinin altında kalan alanın hesabını verir. Bunlara girmeye gerek olmadığını düşünüyorum. Amacım basit bir şekilde anlatmak.)

Yaptığım hesapta bulunan hata=0,5 tir ve bu küçük bir değerdir. Yukarıdaki grafikte x ekseni zamanı gösterir ve birimi saniyedir ama sistemin okuma aralığı 100ms dir. Sıcaklığın hedefe en yakın olduğu zaman, ilk açılıştan yaklaşık 60sn sonrasıdır. İlk açılışla birlikte oluşan hata=4 ve bu her ölçümde azalarak 0,5 e kadar gelmiştir. Kabaca bir ortalama alırsak 60. saniyede hata toplamı=(ortalama hata)2,25 x 600=1350 olur. Küçük miktardaki hatalar toplandığında bulduğumuz hata toplamı oldukça büyük bir rakam oldu. Bu haliye P değeri gibi dimmer bekleme değeri için toplama koyduğumuzda küçük bir değer olan 0,5 in etkisini görürüz. Bunun için dimmer=2500-(P+I) şeklinde değişiklik yapacağız ve dimmer= 2500(750+1350)=400 buluruz. Artık dimmer değeri düştü çıkış gücümüz arttı.

Bu sayede hedef değeri yakalamış olur hatta geçeriz. Geçtiğimizde hata eksi değer almaya başlar ve hata toplamı da azalmaya başlar. Hata toplamı azaldıkça dimmer değeri artar, çıkış azalır. Hatalar ilk açılıştan itibaren toplanıyor, istenen değer çok daha yukarıda bir değer olsun. Haliyle 60sn yerine çok daha uzun bir sürede hedefe yaklaşırız ve hatalar toplamı çok büyük rakamlara ulaşır. Hedef değeri geçip azalmaya başlaması da uzun zaman alır. Bu sistemin kararsız olmasına neden olur. Sadece integral hesabıyla kontrol edilen sistemde 1350 lere çıkan hata toplamının tekrar düşmesi için yine bir 60 sn gerekir.



Böyle bir grafik elde ederiz ki bu istemediğimiz bir şeydir. Bunu önlemek için öncelikle hata toplamını sınırlamamız gerekir. Sınır koysak bile yine de yüksek değerlere çıkacak olan hata toplamını hesabımıza oransalda olduğu gibi bir katsayıyla dahil etmeliyiz. ( I=Ki x Thata ) Hata toplamı sınırını 1500 yaptığımızda ve bu değere ulaştığımızda (I=1500) integralin çıkışa etkisi fazla olacaktır. Katsayıyı 0,8 gibi bir değer belirlersek hata üst sınıra ulaştığında Thata x Ki =1500x0,8=1200 gibi daha uygun bir değer buluruz. (Dimmer 2500 yani 10ms olduğundan bulunan I değeri çok büyük olursa sistemin toparlanması zorlaşır. )

Kısaca tekrar edersek, integral ile bulduğumuz çıkış kazancı, geçmiş hataların toplamıdır. Sistemin kaybını bulur ve hedef değerde sabit kalmasını sağlar.

İntegral Uygulama

Test kutusundaki ısı kaynağının yetersizliği ve ısı kaybı fazlalığından oransal hesapla bulduğumuz çıkış değeriyle hedef değere ulaşamadık. İntegral ile hedef değere ulaşacağız. Bunun için her okuma anında bulduğumuz hatayı bir önceki hatayla toplayarak toplam hata değerine ulaşacağız. Toplam hatayı Ki katsayısı ile çarparak i_dim değerini bulacağız. P_dim ile birlikte dimmer değerini bularak çıkışı ayarlayacağız. Toplam hatayı sınır değerler içinde tutarak çok büyük değerlere ulaşmasını engelleyeceğiz.

uint16_t set_deger=0;
float hata=0.0,  t_hata=0.0;
float kp=1500.0, ki=1.0;
volatile int32_t dimmer=2500;
volatile float p_dim=0.0, i_dim = 0.0;

ISR(INT0_vect){			
		dimmer=2500-(p_dim+i_dim);
		if (dimmer<=0){//dimmer değeri sınırlanıyor
			dimmer=0;
		}
		if (dimmer>=2500){
			dimmer=2500;
		}
		TCCR1B|=(1<<WGM12)|(1<<CS10)|(1<<CS11);//timer çalıştı
		OCR1A=dimmer;//timer kesmesi için gereken süre		
}
int main(void){
	aht10_init();	
	while(1){		
		hata=set_deger-aht10_read();//sıcaklık okundu set değer farkı hata
		p_dim=kp*hata;
		i_dim=(t_hata*ki);
		t_hata+=hata;
		if (t_hata<=-1200){// toplam hata sınırlama
			t_hata=-1200;
		}
		if (t_hata>=1200){// toplam hata sınırlama
			t_hata=1200;
		}		
	}
}

Bu arada Ki değeri çok büyük olursa sıcaklık hedef değerin çok üstüne çıkar hatta sistemde salınıma neden olur. Aşağıda sırasıyla (Kp=3000-Ki=1),(Kp=3000-Ki=1,25) ve (Kp=1500-Ki=1,25) için sonuçlar vardır.




Sistemin istenen değerlere hızlı bir şekilde çıkması ve sabit kalması için PI kontrolü yeterlidir ama ani değişimlere cevap vermekte yetersiz kalabilir. Bunun için türev hesaplamasını dahil etmek gerekir.

Türev(D)

İntegral kontrolde geçmiş hataların toplamıyla çıkışa etki ediyorken türevde gelecek hatalara göre çıkışı kontrol ediyoruz. Bunun için bir önceki okunan değerle anlık okunan değerin farkına bakıyoruz. Yine zaman sabit olduğundan sadece hatayı ele alacağız. Okunan sıcaklık değeri her bir saniyede 0,05 °C  arttığını düşünelim. Bu durumda hata her bir saniyede 0,05 azalacaktır. Bu küçük rakam çok etkili olmayacaktır bu nedenle ve diğer hesaplamalarda (P-I) olduğu gibi çıkışa etkisini değiştirebilmek için bir katsayıyla (Kd) hesaplama yapacağız. Bunun için kullanacağımız formül Pd=(hata-önceki hata) x Kd olacaktır.

Hata miktarı küçük sayı farklarıyla sabit bir değişim içindeyse türevin çıkışa önemli bir etkisi olmayacaktır. Hata ve önceki hata aynı olduğunda türev kontrolün kazancı "0" olacaktır. Durağan bir sistemde hiç etkisi olmazken değişim aniden yükselir veya düşerse bir sonrakinin de bu ani sapmayı devam ettirme olasılığı yüksek olacaktır. Sıcaklık değişimi 0,05 ten  0,1 hatta 1 °C gibi bir fark oluşturduğunda hata ve önceki hata arasındaki fark büyür ve çıkışa etkisi artar. Doğru katsayı seçildiğinde çıkışa etkisi yukarıdaki grafiklerdeki ani değişimleri yumuşatma şeklinde olacaktır. Kp ve Ki de olduğu gibi yüksek değerlerde Kd sistemi salınıma sokacaktır. Doğru Kd değeriyle ani sıcaklık değişimine daha hızlı cevap veren bir kontrol mekanizmasına sahip oluruz. Sabit aralıklarla artan sıcaklık durumunda hata ve önceki hata farkı negatif olacaktır. Bu P ve I ya ters olarak etki edecektir ama ani bir sıcaklık düşüşünde hata-önceki hata farkı pozitif işaret alacaktır. Böylece P ve I ye eklenerek çıkışı yükseltecektir.



Yukarıda dimmer değerini hesaplamakta kullandığım  P,I ve D değerlerinin zamana göre değişimi görünmektedir. Dengeye gelmekte olan düzeneğin kapağını açtığımda yeşil olan türevin hareketi anlatmak istediğimi gösteriyor. Sıfıra yaklaşan P değeri ile birlikte yükseliyor haliyle aynı oranda sıcaklık artışı yaşanıyor. Hata halen var olduğundan P artmaya devam etse de D artmıyor ve tekrar sıfıra yaklaşıyor. Sıcaklık değişimi dengelenip bir miktar aştığında P düşmeye başlarken yine D bu düşüşe destek olarak eksi yönde hareket ediyor. P düşmeye devam ederken D yine sabit salınımlarla sıfıra yaklaşıyor.

Türev Uygulama

Ani değişimlere aynı hızda cevap verebilmek için türev kontrolünü de ekliyorum. Bunun için önceki hatayı tuttuğum bir değişken daha tanımlayıp aşağıdaki şekilde kodları oluşturuyorum. Türev için kullandığımız Kd katsayısı çok büyük olursa ve sistemin okunmasında gürültü varsa sistem kontrolden çıkacaktır.

uint16_t set_deger=0;
float hata=0.0, o_hata=0.0, t_hata=0.0, ohata=0.0;
float kp=1500.0, ki=1.0, kd=1500.0;
volatile int32_t dimmer=2500;
volatile float p_dim=0.0, i_dim = 0.0, d_dim=0.0;

ISR(INT0_vect){			
		dimmer=2500-(p_dim+i_dim+d_dim);
		if (dimmer<=0){//dimmer değeri sınırlanıyor
			dimmer=0;
		}
		if (dimmer>=2500){
			dimmer=2500;
		}
		TCCR1B|=(1<<WGM12)|(1<<CS10)|(1<<CS11);//timer çalıştı
		OCR1A=dimmer;//timer kesmesi için gereken süre		
}
int main(void){
	aht10_init();	
	while(1){		
		hata=set_deger-aht10_read();//sıcaklık okundu set değer farkı hata
		p_dim=kp*hata;
		i_dim=(t_hata*ki);
		d_dim=(o_hata-hata)*kd;
		o_hata=hata;
		t_hata+=hata;
		if (t_hata<=-1200){// toplam hata sınırlama
			t_hata=-1200;
		}
		if (t_hata>=1200){// toplam hata sınırlama
			t_hata=1200;
		}		
	}
}



Grafiklerden ilki Kd=5000 ayarlı, ikinci grafikte Kd=20000 ayarlandı.

Katsayılar

Katsayıları ayarlamak için çeşitli yöntemlerin olduğunu araştırma yaparken görebilirsiniz. Ben ne yazık ki yapamadım. Yukarıda test kutusunun zamana bağlı sıcaklık artış ve düşüşlerini aktarmıştım. Bu değerlere göre bir excel dosyası oluşturdum. Kendi katsayılarımı bulurken en büyük yardımcım bu dosya oldu. Simülasyon demek ne kadar doğru olur bilmiyorum ama öyle de diyebilirim. Belirli aralıklarla ölçüm yapılmış ve bu aralıklarda ısı kaybı ve kazancı olan bir sistemin simülasyonu oldu. Bu dosyadaki  katsayıları, ortam sıcaklığını ve set değeri değiştirerek sonucu görme şansı buldum.

Isı kazancı ve kaybını kutudaki gibi ayarladım. Sadece ani değişiklikler için bir şey yapmadım. Bu nedenle türev kontrolün bu durumlardaki etkisini gözlemleyemeyiz ama hatalı değerlerde sistemi nasıl bozacağını görebiliriz. Bu simülasyonda ve muhtemelen gerçekte de istenen değer değiştikçe oranlarında değişmesi gerekecek. 100ºC ile 250ºC için doğru ayarlar aynı olmayacaktır. Yine tek bir doğru değer olmadığını Kp ve Ki değerlerinin birbirine göre oranlı bir şekilde değişebileceğini göreceksiniz. Sistemin ısı kazancı-kaybı, okuma aralıkları ve okunan değerin gürültüsü de bu oranlarda değişikliğe neden olacaktır. Profesyonel bir iş için deneme yanılma veya böyle bir simülasyonla yapılacak bir iş olmadığını da söyleyebilirim. Amatör bir proje için bu şekilde bulmakta bir sakınca yok. Benim uygulamam için bana göre en uygun katsayılarla oluşan grafik bu şekildedir.



Dosyanın içindeki grafiği paylaşıyorum. Katsayılar bilerek bu şekilde yazıldı. Dosyaya buradan ulaşabilirsiniz.


 

Yukarıda paylaştığım kod parçalarının bire bir aynısını kullanmıyorum. Biraz derli toplu hale getirilmiş halini paylaşıyorum. Kısaca işleyişi açıklayayım, kod üstünde açıklamaları yaptım. Sıfır geçiş tespiti ile Dış kesme oluşuyor. Böylece hem set değeri ayarlamak için ADC0 pini için ölçüm başlıyor. Hem dimmer değeri hesaplanıyor. Ayrıca kesme rutini içinde yer alan bir sayaçla AHT10 için gerekli beklemeleri yapıyorum.  Sayaç 50ms de bir durum değişkenini artırıyor. AHT10 ölçüm başlatma ve okuma 50ms aralıklarla delay olmadan yapılıyor. I2C ile bir veri alındıysa veri alındı bayrağı "1" oluyor buna göre sıcaklık için gerekli hesaplamalar yapılıyor. Sonrasında PID için hesaplamalar yapılıyor. Böylece başa dönüp sonraki kesmede dimmer için gerekli değerler bulunuyor.

Microchip Studio dosyalarına buradan erişebilirsiniz.

/*
* dimmer_aht10_pid.c
*
* Created: 29.01.2022 18:10:53
* Author : haluk
*/
#define F_CPU 16000000UL
#include <avr/interrupt.h>
#include <avr/io.h>
#include "uart.h"
#include "i2c_master.h"
#include <util/delay.h>
#include <stdio.h>

#define MOC_PORT			PORTD
#define MOC_DDR				DDRD
#define MOC_PIN				PORTD4
#define TRIAC_DIR			MOC_DDR|=(1<<MOC_PIN)
#define TRIAC_ON			MOC_PORT&=~(1<<MOC_PIN)
#define TRIAC_OFF			MOC_PORT|=(1<<MOC_PIN)
#define ORAN_H				0.9
#define ORAN_L				0.1
#define MAX_T_HATA			1200

float kp=1500.0, ki=1.0, kd=1500.0;
float hata=0.0, o_hata=0.0, t_hata=0.0;
float sicaklik=0.0;
volatile float p_dim=0.0, i_dim = 0.0, d_dim=0.0;
volatile int32_t dimmer=2600;


volatile uint16_t adc=0;
volatile int16_t set_deger=0;
volatile float sonuc=0.0;

volatile uint32_t zero_sayac=0;
uint32_t  onceki_sayac=0, simdiki_sayac=0 ;
uint8_t durum_sayac=0, onceki_durum=0, simdiki_durum=0;

uint8_t aht_adr=0x38;//aht10adres
char str_data[64];
extern volatile uint8_t veri_geldi;

void PID_hesapla(float pk,float ik,float dk, int16_t set,float temp, int16_t maxhata);
void timer1Con();
void irqZeroCross();
void adc_init();
void aht10_init();
void aht10_trig();
void aht10_read();
float aht10_get_temp();

ISR(INT0_vect){	//10ms de bir kesme oluşur.
	if (PIND&(1<<PIND2)){//yükselen kenar kontrol
		if (PIND&(1<<PIND2)){			
		dimmer=2600-(p_dim+i_dim+d_dim);
		if (dimmer<=300){//sıfır geçiş ofset
			dimmer=300;
		}
		if (dimmer>=2600){//sıfır geçiş ofset
			dimmer=2600;
		}
		TCCR1B|=(1<<WGM12)|(1<<CS10)|(1<<CS11);//timer1 çalıştı
		//OCR1A=2600;//dimmer ofset elle girilerek konrtol edildi
		OCR1A=dimmer;//dimmer değeri kadar bekleme
		zero_sayac++;//her sıfır geçişte sayaç artar 10ms de bir
		}
	}
}
ISR (TIMER1_COMPA_vect){
	TRIAC_ON;
	_delay_us(10);
	TRIAC_OFF;
	TCCR1B=0x00;//timer1 durdu
}
ISR (ADC_vect){//her sıfır geçişte ADC0 ölçülüyor
	adc=ADCW;
	sonuc=(float)((sonuc)*ORAN_H)+(float)(adc*ORAN_L);
	set_deger=((uint16_t)sonuc)>>2;//0-255 arası değer alır	
}


int main(void){
	TRIAC_DIR;
	irqZeroCross();
	timer1Con();
	uart_basla(115200);
	i2c_init();
	aht10_init();
	adc_init();
	//set_deger=40;		
	while(1){		
		simdiki_sayac=zero_sayac;
		if ((uint32_t)(simdiki_sayac-onceki_sayac)>=5){//50ms de bir artar						
			onceki_sayac=zero_sayac;
			durum_sayac=(durum_sayac+1)&1;			
		}			
		switch (durum_sayac){//aht10 i2c iletişimin sağlıklı yürümesi için gereken beklemeler ayarlandı.
			//her seçenek arası 50ms
			case 0:
				if (onceki_durum!=durum_sayac){
					aht10_trig();//aht10 ölçüm başlatıldı			
					onceki_durum=durum_sayac;
				}
				break;
			case 1:
				if (onceki_durum!=durum_sayac){
					aht10_read();//aht10 ölçülen veri okundu						
					onceki_durum=durum_sayac;
				}
				break;
			default:
			uart_gonder('d');
				durum_sayac=0;
				break;
		}
		if (veri_geldi==1){
			sicaklik=aht10_get_temp();//sıcaklık bilgisini alındı
			PID_hesapla(kp,ki,kd,set_deger,sicaklik,MAX_T_HATA);//pid hesaplandı
			//sprintf(str_data,"dimmer:%li pdim:%.2f idim:%.2f ddim:%.2f\n",dimmer, p_dim,i_dim,d_dim);
			sprintf(str_data,"sic:%.2f set:%d\n",sicaklik,set_deger);
			uart_dizi(str_data);
		}	
				
	}
}
void PID_hesapla(float pk,float ik,float dk, int16_t set,float temp, int16_t maxhata){
	hata=set-temp;
	p_dim=(pk*hata);
	i_dim=(t_hata*ik);
	d_dim=(hata-o_hata)*dk;
	o_hata=hata;
	t_hata+=hata;
	if (t_hata<=-maxhata){
		t_hata=-maxhata;
	}
	if (t_hata>=maxhata){
		t_hata=maxhata;
	}
}
void irqZeroCross(){
	DDRD&=~(1<<PORTD2);//pd2 giriş yapıldı
	//PORTD|=(1<<PORTD2);//pd2 dahili pull-up
	EICRA|=(1<<ISC01)|(1<<ISC00);//pd2 yükselen kenar
	EIMSK|=(1<<INT0);//pd2
	sei();// tüm kesmeler açık
}
void timer1Con(){
	TCCR1B|=(1<<WGM12)|(1<<CS10)|(1<<CS11);// ctc mode prescaler 64 1sn 250000
	TIMSK1|=(1<<OCIE1A);//ocra eşleşme kesmesi açık
	OCR1A=249;//1ms de kesme
	sei();// tüm kesmeler açık
}
void adc_init(){
	cli();
	ADMUX=(1<<REFS0);
	ADCSRA=(1<<ADEN)|(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADIE)|(1<<ADATE);//oto triger
	ADCSRB |=(1<<ADTS1);//dış kesme adc triger
	ADCSRA |=(1<<ADSC);
	sei();
}
void aht10_init(){	
	uint8_t aht_res=0xBA;//soft reset
	uint8_t aht_init[]={0xE1,0x08,0x00};//init
	i2c_send_data(aht_adr,aht_res, N_REPEAT);//soft reset
	_delay_ms(100);	
	i2c_send(aht_adr,aht_init,3, N_REPEAT);//init komutu
	_delay_ms(100);
}
void aht10_trig(){
	uint8_t aht_trig[]={0xAC,0x33,0x00};//ölçüm başlatma
	i2c_send(0x38,aht_trig,3,N_REPEAT);//ölçme tetik
}
void aht10_read(){
	i2c_read(aht_adr,6);// veri okuma
}
float aht10_get_temp(){	
	uint8_t aht_data[6];
	uint8_t poz=0;
	uint32_t aht_temp=0;		
	do {			
			aht_data[poz]=i2c_oku();
			poz++;	
		} while (i2c_gelen());	
	if (poz!=6) return sicaklik;//6 byte veri gelmeli hatalı gelirse bir önceki değer döner		
	aht_temp=((uint32_t)(aht_data[3]&0x0F)<<16)|((uint16_t)aht_data[4]<<8)|(aht_data[5]);	
	return ((float)aht_temp/5242.88)- 50;
}
/////////////////////////////////////////////////////////////////////////////
/*
 * 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;
volatile uint8_t veri_geldi=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){
		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(){
        veri_geldi=0;
	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; 
			veri_geldi=1;
			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