Sayfanın PDF versiyonu: DMA ile ADC Okuma ve Filtreleme (.pdf)
DMA ile ADC Okuma ve Filtreleme
Gerçek dünyadaki analog bilgilerin işlenebilmesi için önce sayısal verilere dönüştürülmesi gerekir. Bu sayısal veriler de kullanılabilir hale getirmek için önce filtrelenmeli ardından kalibre edilmelidir. Bu dönüştürme ve filtreleme işlemleri sırasında çok sayıda örnek alınmalı ve işlemcinin bir miktar işlem gücünü harcayarak, örnekler üzerinden hesaplama yapılmalıdır.

Alternatif bir yöntem olarak, işlem gücü harcamadan, DMA ile paralelde örnekleme ve filtreleme yapabiliriz. Ek bir fayda olarak, sayısal verilerimizi bir yandan filtrelerken, bir yandan çözünürlüklerini arttırabiliriz.

Bu dökuman da işlem gücünüzü harcamak yerine DMA’ yı nasıl kullanabileceğinizi, ve okunan verilerin çözünürlüğünü nasıl arttırılabileceğini STM32F100 mikrodenetleyicisi ile anlatacağız.
Analog Verilerin Sayısallaştırılması
Günümüzde hemen hemen tüm mikrodenetleyici içinde bulunan ADC (Analog-Digital Converter), dış ortamdaki analog verileri bir sayısal değere dönüştürür.

Önce, dış ortamdaki ölçülecek değerler (ısı, ışık, ivme, basınç vb.) bir algılayıcı (sensör) ile elektriksel gerilime (frekans veya darbe -pulse- de olabilir) dönüştürülür. İşte bu analog gerilimin mikrodenetleyici içerisinde işlenebilecek şekilde sayısal değere dönüştürülmesi ADC tarafından gerçekleştirilir. Bu aşamadan sonra sayısal veri üzerinde çalışılabilir.

Bir analog gerilimi, sayısal değere dönüştürdüğümüzde aslında, mevcut gerilim değeri üzerinden bir örnek almış oluruz. Fakat bu gerilim, üzerinde bir gürültü de barındırdığından, örnek aldığımız noktada sinyalin gerçek ortalama değerini bulabilmiş sayılmayız. Bunun için sinyalden çok sayıda örnek alıp, bu örnekleri filtrelememiz de gerekiyor.
STM32F100 ile Sayısallaştırma
STM32VLDiscovery kartı üzerinde STM32F100 mikrodenetleyicisi, bu mikrodenetleyici üzerinde de 12-bit’ lik, toplamda 16 adet ADC kanalı bulunmaktadır. Biz bunların 8 tanesini kullanarak, 8 kanal analog veri okuyacağız. Kullanacağımız analog ölçüm kanallarının bağlı olduğu pinler aşağıdaki tabloda verilmiştir.

Kanal Pin
0 PA0
1 PA1
2 PA2
3 PA3
4 PA4
5 PA5
6 PA6
7 PA7

12-bit’ lik ADC ile 0~3.3V arasındaki gerilim değeri, 0~4095 arasında sayısal değerlere dönüştürülür. Buna göre, sayısal değerdeki her bir değişim, gerilim seviyesindeki 0.8mV değişime karşılık gelmektedir.

3.3V / 4096 = 0.8mV
Bağlantılar
Test için tüm kanallara, potansiyometreli bir gerilim bölücü üzerinden giriş veriyoruz.


Verilerin Okunması
ADC ile analog verilerin okunması kısmına girmeyeceğiz. Bununla ilgili çok sayıda örneğe internetten ulaşabilirsiniz zaten.

Şimdilik sadece, ADC den okunan verileri, UART3->PB10 (115200,8n,1) üzerinden, ASCII formatında PC’ ye göndereceğiz. Böylece verileri kaydedebilecek ve inceleyebileceğiz.

Proje kaynak kodlarına buradan ulaşabilirsiniz. Fark edeceğiniz gibi, henüz DMA kullanmaya başlamadık. Önce, DMA kullanmadan nasıl çalışıyoruz, sonra da DMA bize ne faydalar sağlayacak bunu göreceğiz.

Aşağıda, PC’ ye gelen verileri görebilirsiniz. Sırasıyla 8 kanal veri virgül ile ayrılmış şekilde gönderilmektedir.


Bu verilerin grafiğini çizersek, okunan değerlerde gürültülerin olduğunu göreceğiz.


Veriler için minimum değer 2040 maksimum değer 2070. Buna göre gürültünün gerilim değeri;

0.8mV * ( 2070 – 2040 ) = 24mV olacaktır.

Bu, uygulamada sürekli karşımıza çıkan ve çözmemiz gereken bir sorundur. Çözümler arasında en sık kullanılanı, ortalama alma yöntemidir. Yani örnek sayısı arttırılarak, bu örneklerin ortalama değeri filtrelenmiş çıkış olarak kullanılabilir.
uint16_t ADC_GetData( uint8_t channel ){
    uint8_t i;
    uint16_t retVal = 0;
    for(i=0;i < ADC_CHANNEL_DATA_COUNT;i++){
        retVal += adcData[channel][i];
    }
    return retVal/ADC_CHANNEL_DATA_COUNT;
}                        
                    
Okunan verilerin grafiğini çizdiğimizde, gürültülerden büyük oranda kurtulduğumuzu görebiliriz.


Veriler için minimum değer 2038 maksimum değer 2046. Buna göre gürültünün gerilim değeri;

0.8mV * ( 2046 – 2038 ) = 6.4mV olacaktır.

Denemek isterseniz projenin mevcut haline buradan ulaşabilirsiniz.
DMA Nedir?
Doğrudan Bellek Erişimi (Direct Memory Access), çevresel birimlerden RAM belleğe, RAM bellekten çevresel birimlere veya RAM bellekten RAM belleğe veri aktarımını CPU kullanmadan yapan donanımdır.

Örneğin, ethernet paketini oluşturdunuz ve bu paketi ethernet çevresel birimine göndermek istiyorsunuz. Eğer DMA’ nız varsa ve kullanmıyorsanız, 1500 byte civarında olabilecek bu veriyi sıradan yöntemler ile kopyalamak, CPU’ nun işlem gücünü boşa harcamanız anlamına gelecektir. Ethernet üzerinden alınan paketleri, çevresel birimden belleğe alırken de benzer durum oluşacaktır. Veya sadece RAM bellekteki uzun bir veriyi, başka bir RAM alanına kopyalamak istiyorsunuz. Bu durumda da DMA size yardımcı olmak için oradadır.

Biz ise DMA’ yı, ADC okumalarının CPU’ yu yormadan gerçekleşmesi için kullanacağız.
DMA Konfigürasyonu
DMA’ yı kullanabilmek için öncelikle DMA’ nın saat sinyalini açmamız gerekiyor.
RCC->AHBENR  |= 0x00000001;//DMA1 clock enable
                    
Daha sonra veri transferinin çevresel birimden RAM belleğe olacağını belirtiyoruz.
DMA1->IFCR |= 0x00000001; //Clear Interrupt flags
DMA1_Channel1->CCR = 0x05A0;//Set source data length and direction
                    
Ardından, kaç byte veri transferi yapılacağını söylemeliyiz.
DMA1_Channel1->CNDTR = ADC_CHANNEL_COUNT * ADC_CHANNEL_DATA_COUNT;//count
                    
DMA kullanmadığımız durumda, ADC datasının hangi adresten hangi adrese kopyalanacağını biliyor ve buna göre kodu yazıyorduk. DMA kullanırken bu bilgileri DMA’ ya bildirmeliyiz.
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;//Source address
DMA1_Channel1->CMAR = (uint32_t)adcData;//Destination start address
                    
Son olarak DMA’ yı aktif ediyoruz.
DMA1_Channel1->CCR |= 0x0001;//Enable channel
                    
Bu konfigürasyonda ADC’ ye de “Beni rahatsız etme, vekilim DMA’ yı kullan” demek için;
ADC1->CR2 = 0x000E7103; //Cont. Conversion mode, DMA enabled
                    
Artık, ADC her dönüştürme sonunda kesme üretmeyecek ve DMA’ yı tetikleyerek, sonuçları adcData[][] değişkenine sırayla yazacak. Kesmelerden de kurtulduğumuz için CPU çevrim sonuçlarını kopyalamak veya yeni çevrim başlatmak için hiç zaman kaybetmeyecek.

Şimdi sonuçlara tekrar bakalım;


Görüldüğü gibi, bu yöntemde de filtrelenmiş veriyi elde ettik. Burada da gürültüyü hesaplarsak;

0.8mV * ( 2046 – 2038 ) = 6.4mV buluruz.
Çözünürlüğün Arttırılması
12-bit’ lik bir ADC ile 0~4095 arasında bir sayısal değer alabileceğimizi söylemiştik. Bu da her bir sayısal değerin 0.8mV’ luk bir değişime karşılık gelmesi demekti. Bir adım daha ileri giderek, bu çözünürlüğün arttırılmasına çalışalım.

Proje boyunca her kanalda 16 örnek topluyor ve bu örneklerin ortalamasını alıyorduk. Bunun yerine, 16 örneğin toplamını sonuç olarak kullanmayı deneyelim. Bu durumda 0~65535 arasında değer üreten 16-bit’ lik bir ADC sonucum olacaktır.
uint16_t ADC_GetData( uint8_t channel )
{
    uint8_t i;
    uint16_t retVal = 0;
    for( i = 0; i < ADC_CHANNEL_DATA_COUNT; i++ )
    {
        retVal += adcData[i][channel];
    }
    return retVal;
}
                    



Buna göre, minimum(32828) ve maksimum(32914) değerler arasındaki fark artmış görünüyor. Ancak, bu durumda her birime karşılık gelen gerilim değeri 0.8mV değil,

3.3V / 65536 = 0.05mV olacaktır. Buna göre bir hesap yaparsak;

0.05mV * (32914 – 32828) = 4.3mV buluruz.

Dolayısıyla çözünürlüğümüz 16-bit olduğu halde, gürültü -beklediğimiz gibi- artmamıştır.
Sıra Sizde
Projenin kaynak kodlarına buradan ulaşabilirsiniz.

Peki şimdi neler yapılabilir?
  • 16-bit’ lik sonuçlar üzerindeki gürültüyü filtreleyebilirsiniz.
  • 16-bit veya 12-bit’ lik sonuçlar üzerinde bir alçak/yüksek geçiren filtre uygulayabilirsiniz.