Arduino’ da Derinlemesine ADC
|Merhaba,
Bu konuda gömülü sistemlerde sıklıkla kullandığımız Arduino’ da bulunan ADC’ de daha hassas ayarlama nasıl yapıldığına bakacağız. Normalde Arduino fonksiyonlarını kullanarak pin setleme ve okuma yapabiliyoruz. Hazır Arduino fonksiyonlarını kullandığımızda ADC’ nin çalışması için gerekli ayarlamalar daha önceden belirlenen şekilde ayarlanıyor. Bu durum hızlı çözümler için ideal olsa da bazı durumlarda yeterli olmayabiliyor. Örneğin biz yapacağımız uygulamada örnekleme süresinin farklı olmasını bu sebeple ADC’ de prescaler ayarını değişmek istiyoruz. Ya da yapacağımız okuma işlemi için ana yazılımın beklemesini istemiyoruz. Bu durumda ADC kesme (interrupt) modda çalıştırabiliriz. Ancak bu ayarları yapmak için Arduino’ da bir ayar mevcut değil. Bu sebeple mikrodenetleyicinin sahip olduğu ADC registerlerini kullanarak ayar yapacağız. Başlangıç seviyesindeki kişiler için bu konu biraz ağır olabilir. Konu içerisinde şu başlıklara yer vermeye çalışacağım:
- Prescaler ayarı
- Referans voltajı ayarı
- Dahili sıcaklık ve gerilim sensörü kullanımı
- ADC ile ana programı durdurmayacak şekilde okuma yapma
- ADC kesmesi kullanma
Aşağıda anlatımı geçecek konular Arduino Atmega328p (Uno, Nano) denetleyicisi için verilmiştir. Atmega2560’ da (Mega) bazı küçük farklılıklar olabilir. Sorun çıkması halinde Atmega2560’ ın datasheet’ inde ilgili register’ e bakarak kullanmak sorunun çözülmesine yardımcı olabilir. Atmega328p’ nin datasheet’ ine bağlantıdan ulaşabilirsiniz. Datasheet biraz uzun ancak bu konuda bizi ilgilendiren kısımlar sadece ADC ile ilgili olanlardır.
Prescaler Ayarı
Arduino’ da bir ADC dönüşümü 13 saat döngüsü (clock cycle) sürer. Bu ilk ayarlar hariç süredir. İlk ayarlamalardan sonra yapılan dönüşüm 25 saat döngüsü sürecektir. Burada baştan belirtmek gerekirse prescaler ayarını düşürmek, saat döngüsü süresini azaltmayacaktır. Sadece iki örnek arasında alınan süreyi azaltacaktır. Saat döngüsü süresini değiştirmek için çözünürlüğün değişmesi gerekir. Arduino üzerindeki ADC sadece 10 bit çözünürlükte ölçüm yapabilmektedir. ADC için yapılacak dönüşüm hesabı aşağıdaki şekilde gerçekleştirilir:
Tahmin ettiğiniz üzere formülde verilen 16×10^6 ifadesi Arduino’ nun çalışma frekansını ifade etmektedir. 1/16×10^6 , 1 clock cycle için geçen süre olduğundan bu süreyi ADC için gerekli saat döngüsü ve prescaler ile çarparak dönüşüm için geçen süreyi bulabiliriz.
Arduino’ da ADC prescaler ayarı ADCSRA register’ i ve ADSPX bitleri kullanılarak gerçekleştirilir. Kullanımı basitçe şu şekilde şekildedir.
ADCSRA |= bit (ADPS0); // 2
ADCSRA |= bit (ADPS1); // 4
ADCSRA |= bit (ADPS0) | bit (ADPS1); // 8
ADCSRA |= bit (ADPS2); // 16
ADCSRA |= bit (ADPS0) | bit (ADPS2); // 32
ADCSRA |= bit (ADPS1) | bit (ADPS2); // 64
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // 128
Arduino ile ADC ölçümü için düşük prescaler değerlerinde ölçüm yapmak hatalı sonuçlar verebilmektedir. Nick gammon’ un sitesinde prescaler ayarları örnek bir yazılım üzerinden test ederek anlatışmıştır. 16 prescaler değerinden küçük değerlerde ölçümlerin hatalı olmaya başladığı gözlemlenmiştir. Bir örnek yazılım içerisinde prescaler değişimini şu şekilde yapabiliriz.
void setup() {
// put your setup code here, to run once:
ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // prescaler bitlerini temizle
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // 128 prescaler ayarla
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
int val = analogRead(A0); // adc ile okuma yap
Serial.println(val); // seri portta göster
delay(100);
}
Arduino’ da tek ADC olduğu için ayarı bir kez yaptıktan sonra tüm analog pinlerde okuma gerçekleştirebiliriz. Her pin için farklı bir ayar yapılmaz. Prescaler belirleme işlemi bit maskeleme ile yapıldığı için en başta bitleri temizlemek yanlış yapma ihtimalini en aza indirir.
Referans Gerilimi Değişimi
Analog referans gerilimi değişimine neden ihtiyaç duyarız? Bunun birden çok sebebi olabilir. İlk sebebi çözünürlüğü arttırmak ve daha hassas ölçüm yapmak. Referans gerilimi ne kadar küçük olursa ADC’ nin adım aralığı yani çözünürlük daha küçük olacaktır. Tabi burada giriş gerilimi referans gerilimini aşmayacak şekilde bir gerilim belirlenmelidir. İkinci sebep kullandığımız denetleyicide dahili referans gerilim kaynağı çok stabil olmayabilir. Özellikle pinlerden çok çıkış kullanıldığı durumlarda bu tür sorunlar yaşanabilir. Bu durumlarda ADC için dışarıdan referans gerilim verilebilir ve stabil çalışması sağlanabilir. Üçüncü sebep de dahili sensörler kullanılırken daha düşük referans gerilim kaynağına ihtiyaç duyulabilir. Buna ilerleyen kısımlarda daha detaylı değineceğim.
Yukarıdaki tablo ADCMUX register’ indeki VREF değişim bitlerini ifade eden REFSX bitlerinin kombinasyonlarını göstermektedir. Ancak Arduino’ da bu register’ i kullanmadan da ADC referans gerilimi değiştirilebilmektedir.
Arduino’ da “analogReference” fonksiyonu, adından da anlaşıldığı üzere ADC’ nin referans voltajı değiştirmek için kullanılır. 3 farklı parametre alabilir.
- DEFAULT (Vcc Gerilimi)
- INTERNAL (1.1V)
- EXTERNAL (Dışarıdan Harici Gerilim)
Arduino’ ya verilecek referans gerilim en yüksek VCC gerilimi değerini yani 5V’ u geçmemelidir. Aksi taktirde denetleyiciye zarar verebilirsiniz. Harici referans gerilimi, Arduino üzerindeki “ref”, “aref” yada “vref” isimli pin yardımı ile verilebilir. Kullanıma örnek olması açısından aşağıdaki yazılıma bakabilirsiniz.
void setup() {
// put your setup code here, to run once:
analogReference(DEFAULT); // analog referans voltaji 5V ayarli
pinMode(A0, INPUT);
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
int val = analogRead(A0);
Serial.println(val);
delay(100);
}
Dahili Sıcaklık ve Gerilim Sensörü Kullanımı
Atmega328p denetleyicisi üzerinde dahili olarak bir sıcaklık sensörü ve bir de besleme gerilimini ölçen gerilim sensörü mevcut. Bunları kullanabilmek için ADC üzerinde bazı ayarlamalar yaparak sensörlerin bağlandığı ADC kanallarını aktif etmeliyiz. Ayrıca mikrodenetleyicinin datasheet’ inde belirtildiği üzere bu sensörleri kullanabilmek için ADC referans gerilimini 1.1V olarak ayarlamalıyız. Böylece çözünürlük yeterli değerde olacak ve hesaplama sonucu doğru olacaktır. Aşağıdaki tabloda ADCMUX registeri için MUX bitlerinin kombinasyonları görülmektedir.
Tabloda da görüldüğü üzere ADC8 girişi sıcaklık sensörüne bağıdır. Bu sebeple sıcaklık okumak için ADC8 kanalını aktif etmeliyiz.
const float InternalReferenceVoltage = 1.096; // as measured
void readTemp()
{
ADMUX = bit (REFS0) | bit (REFS1) | 0x08; // temperature sensor
delay (20); // let it stabilize
bitSet (ADCSRA, ADSC); // start a conversion
while (bit_is_set(ADCSRA, ADSC))
{ }
int value = ADC;
Serial.print ("Temperature = ");
Serial.println (value - 320);
}
void readVolt()
{
ADMUX = bit (REFS0) | bit (MUX3) | bit (MUX2) | bit (MUX1);
delay (20); // let it stabilize
bitSet (ADCSRA, ADSC); // start a conversion
while (bit_is_set(ADCSRA, ADSC))
{ }
float results = InternalReferenceVoltage / float (ADC + 0.5) * 1024.0;
Serial.print ("Voltage = ");
Serial.println (results);
}
void setup ()
{
Serial.begin (115200);
ADCSRA = bit (ADEN); // turn ADC on
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // Prescaler of 128
delay(20);
} // end of setup
void loop () {
readTemp();
delay(100);
readVolt();
delay(1000);
}
ADC ile Ana Yazılımı Bekletmeden Okuma Yapma
Daha önce de belirttiğimiz gibi ADC ile yapılan ölçümler zaman alan işlemler. Bu sebeple ADC ile bir analog kanaldan okuma yaparken ana yazılım ADC’ nin işlevinin bitmesini beklemektedir. Bu durumun daha iyi anlaşılması için aşağıdaki görsele bakabilirsiniz.
Görüldüğü gibi ADC dönüştürme işlemi bitene Arduino hiçbir şey yapmadan bekliyor. Eğer ihtiyaç duyduğumuz yazılım zaman gecikmelerine karşı duyarlı ise bu durum bizim için sorun oluşturabilir. Bu gibi durumlarda kullanılabilecek çözümlerden bir tanesi burada kullanacağımız yapıdır. Bu yapıda ADC’ ye başlaması için komut verip bitmesini beklemeden diğer işlemlere devam edebilecektir. ADC işlevini tamamladığında bir ADCSRA registeri’ ndeki ADSC bitini sıfırlar. Bunu kontrol ederek işlem tamamlandığında dönüştürülmesi tamamlanan ADC değerini “ADC” registeri’ nden okuyacağız. Aslında yapacağımız işlem ile akış diyagramı yukarıda gösterildiği şekilden aşağıda gösterildiği şekle dönüşecektir.
Bu işlemi en basit olarak aşağıdaki şekilde gerçekleştirebiliriz.
bool working = false; // adc nin calisma durumu bayrak degiskeni
int val = 0;
void setup() {
// put your setup code here, to run once:
pinMode(A0, INPUT); // a0 pinini giriş olarak ayarla
analogRead(A0); // adc kanalı olarak a0 belirle
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
if (!working) // adc islemi bittiyse tekrar baslat
{
bitSet(ADCSRA, ADSC);
working = true; // calisma durumunu true olarak kaydet
}
if (bit_is_clear(ADCSRA, ADSC)) //adc islemi bittiyse degeri oku
{
val = ADC;
working = false; // calisma durumu false olarak kaydet
}
// diger islemler burada
digitalWrite(13, !digitalRead(13));
Serial.println(val);
delay(500);
}
Verilen örnek A0 kanalını aktif etmek için “analogRead” fonksiyonu setup kısmında bir kez çalıştırılmıştır. Böylece A0 kanalı ayarlanmış sonradan değişiklik yapılmadığı için hep aynı kanalda ölçüme devam edilmiştir. Kanal seçimini sonradan değiştirmek için bu işlemi register ile’ de yapabiliriz.
int adcPin = 0;
ADMUX = bit (REFS0) | (adcPin & 0x07); // referans gerilimi Vcc ve girişi A0 ayarla
Bu komut referans gerilimi olarak Vcc ve ADC multiplexer’ inde kanal 0 seçmemizi sağlar. Pini değiştirerek farklı adc kanalları seçebilir ve okuma gerçekleştirebiliriz. Tabi bunu “bitSet” komutundan önce yapmalıyız.
ADC ile Kesme (Interrupt) Kullanmak
Kullanımından son bahsedeceğimiz yapı da ADC’ yi kesme fonksiyonu ile kullanacağımız yapıdır. Bu yapıda ADC çevrime başladıktan sonra bitti mi diye kontrol etmeyeceğiz. Çünkü ADC işlemini bitirdiğinde Arduino o anda yaptığı işlemi bırakıp kesme fonksiyonuna gidecek ve ADC değerini okuyacaktır. Böylece ana fonksiyonda yapılan işlemler ADC sebebi ile beklemeyecek ve zamana karşı hassas olan işlemlerde sorun yaşama ihtimali en aza indirilecektir. ADC’ yi kesme modunda çalıştırmak için ADCSRA registerinde ADCS bitinin yanında ADIE bitini de 1 olarak ayarlayacağız. Böylece ADC işlemini bitirdiğinde “ADC_vect” isimli vektör fonksiyonuna gidecektir. ADC’ yi kesme modunda kullanabileceğiniz basit yazılım aşağıdaki gibidir.
bool working = false; // adc nin calisma durumu bayrak degiskeni
int val = 0;
// adc kesme fonksiyonu
ISR(ADC_vect)
{
val = ADC;
working = false; // calisma durumu false olarak kaydet
}
void setup() {
// put your setup code here, to run once:
pinMode(A0, INPUT); // a0 pinini giriş olarak ayarla
analogRead(A0); // adc kanalı olarak a0 belirle
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
if (!working) // adc islemi bittiyse tekrar baslat
{
ADCSRA |= bit(ADSC) | bit(ADIE); // adc' yi kesme modunda baslat
working = true; // calisma durumunu true olarak kaydet
}
// diger islemler burada
digitalWrite(13, !digitalRead(13));
Serial.println(val);
delay(500);
}
ADC kanalını değiştirmek için yine üstteki yapıda olduğu gibi ADMUX register’ ini kullanabiliriz. Kesme fonksiyonu içerisinde yeni dönüşüm işlemini başlatmadan önce kanal seçimini yapabiliriz.
Bu konu içerisinde anlatacaklarım bu kadardı. Konu içerisinde anlatılan bütün yapıları elimden geldiğince birleştirdiğim bir uygulamaya bağlantıdan ulaşabilirsiniz.
İyi çalışmalar dilerim…