26 Kasım 2015 Perşembe

On-line IIR Filtreleme ve STM32F407


IIR filtreleme, mikrodenetleyici uygulaması olarak kullanıldığında FIR filtrelemeye oldukça benzerdir. IIR (Infinite Impulse Response) filtreleme ile ilgili bir uygulama örneği verilecektir.


Matematiksel olarak IIR Filtreleme şu şekilde ifade edilir;


Burada katsayılar (ağırlıklar) “am” ve “bn”, “x” giriş örnekleri ve “y” filtre sonucunu gösterir. Katsayılar FIR katsayıları için kullanılan Fourier dönüşümün tersinden daha karışık bir algoritma kullanılarak belirlenir. Genel olarak “M” , “N”e eşit değildir.

                Bir filtre çıkışının hesaplaması için gereken yol figür 1’de gösterilmiştir. 2 adet dairesel tampon (circular buffer), biri giriş örnekleri için “x” olarak ve diğeri çıkış sonucu içindir “y” olarak isimlendirilmiştir. Yeni sonuç “yk” iki konvolüsyon beraber eklenerek oluşturulmuştur. Üstteki konvolüsyon giriş örnekleri olan “x”i ve katsayıları temsil eden “a”yı,  alttaki konvolüsyon sonuç olarak belirtilen “y”yi ve katsayıları temsil eden “b”yi içerir. Yeni sonuçlar “yk” filtreleme sonrası DAC’a gönderilir.


Figür 1. IIR filtrelemenin grafiksel olarak gösterimi

Mikrodenetleyicide uygulanan filtreleme FIR filtreleme uygulamasının temel halidir. Program Figür 2.de listelenmiştir. Özelliklerin kurulum fonksiyonları FIR filtrelemeyi anlattığımız önceki yazılarımızdaki ile aynıdır. Bu sebepten dolayı burada tekrar anlatılmayacaktır.

Kesme fonksiyonunda da kullanılacak olması sebebi ile genel (global) olarak tanımlanmış 2 (iki) dairesel tampon bulunur. Giriş örnekleri için olan “x1” tamponu integer tipte tanımlanmıştır buna rağmen sonuç çıkış tamponu “y1” float tipte tanımlanmıştır. Hassasiyet şunun için gereklidir; IIR integer sonuçların kullanımdan kaynaklı sayısal hatalar, performansı düşük filtreleme, sayısal istikrarsızlıklar ve osilasyonlara (salınımlara) yol açabilir. Çıkış tamponu da integer olabilir, ama katsayılar FIR filtreleme örneğindeki olduğu gibi yukarı ölçekli olmalıdır.

2 (iki) sinyalinde de eş zamanlı filtrelemesine izin vermek için 2 (iki) parça dairesel tampon bulunur. Tampon uzunlukları biraz abartılmıştır.

Neyse ki bizim için, katsayılar tabloda verilmiştir. Bu örnek programda kullanmak amacı ile katsayı değerleri referanstan kopyalanmıştır[1]. 4ncü dereceden “Chebishew” filtre karakteristikleri uygulanmıştır. Örnekleme frekansının köşe frekansı 0.025 ve bant geçiren kısmın ripple oranı %0.5 olarak kullanılmıştır. Katsayılar;Figür 1. IIR filtrelemenin grafiksel olarak gösterimi

Mikrodenetleyicide uygulanan filtreleme FIR filtreleme uygulamasının temel halidir. Program Figür 2.de listelenmiştir. Özelliklerin kurulum fonksiyonları FIR filtrelemeyi anlattığımız önceki yazılarımızdaki ile aynıdır. Bu sebepten dolayı burada tekrar anlatılmayacaktır.

Kesme fonksiyonunda da kullanılacak olması sebebi ile genel (global) olarak tanımlanmış 2 (iki) dairesel tampon bulunur. Giriş örnekleri için olan “x1” tamponu integer tipte tanımlanmıştır buna rağmen sonuç çıkış tamponu “y1” float tipte tanımlanmıştır. Hassasiyet şunun için gereklidir; IIR integer sonuçların kullanımdan kaynaklı sayısal hatalar, performansı düşük filtreleme, sayısal istikrarsızlıklar ve osilasyonlara (salınımlara) yol açabilir. Çıkış tamponu da integer olabilir, ama katsayılar FIR filtreleme örneğindeki olduğu gibi yukarı ölçekli olmalıdır.

2 (iki) sinyalinde de eş zamanlı filtrelemesine izin vermek için 2 (iki) parça dairesel tampon bulunur. Tampon uzunlukları biraz abartılmıştır.

Neyse ki bizim için, katsayılar tabloda verilmiştir. Bu örnek programda kullanmak amacı ile katsayı değerleri referanstan kopyalanmıştır[1]. 4ncü dereceden “Chebishew” filtre karakteristikleri uygulanmıştır. Örnekleme frekansının köşe frekansı 0.025 ve bant geçiren kısmın ripple oranı %0.5 olarak kullanılmıştır. Katsayılar;


Bu değerler, programın başında tanımlama bölümü içinde kodlanmıştır.

ADC ve DAC’ın ana program içerisinde kurulumları yapılmıştır. Periyodik örnekleme ve sinyal üretimi için Timer kullanılmıştır. Kesme denetleyicisi NVIC, ADC’den gelen kesme isteği için aktif hale getirilmiştir. Daha sonra mikrodenetleyici sonsuz döngü içerisinde zaman harcayarak çalışmasına devam etmektedir(bu döngüde periyodik olarak butonları ve LED’leri kontrol eder).

#include "stm32f4xx.h"

int x1[4096], x2[4096], xyPtr; // giriş için dairesel tamponların tanımlanması
float y1[4096], y2[4096]; // çıkış için dairesel tamponların tanımlanması

// IIR katsayılarının kurulumu: 4ncü derece Chebishew Alçak geçiren [1]
// 0.5%, 250Hz’de -3 dB örnekleme frekansı
float a[5] = {1.504626e-5, 6.018503e-5, 9.027754e-5, 6.018503e-5, 1.504626e-5};
float b[5] = {0 , 3.725385e0 , -5.226004e0 , 3.270902e0 , -7.705239e-1};

int main ()
{
GPIO_setup(); // GPIO kurulumu
DAC_setup(); // DAC kurulumu
ADC_setup(); // ADC kurulumu
Timer2_setup(); // Timer 2 kurulumu
NVIC_EnableIRQ(ADC_IRQn); // ADC kesmesi için NVIC aktif edilir

// zaman gecikmesi
while (1)
{
//LED yak
if (GPIOE->IDR & 0x0001) GPIOA->ODR |= 0x0040;
// değil ise LED söndür
else GPIOA->ODR &= ~0x0040;
};
}


// kesme fonksiyonu
void ADC_IRQHandler(void) // bu kısım yaklaşık 6uS sürer
{
GPIOE->ODR |= 0x0100; // PE08 high yap
x1[xyPtr] = ADC1->DR; // ADC -> dairesel tampon x1
x2[xyPtr] = ADC2->DR; // ADC -> dairesel tampon x2
float conv = 0; // toplama işlemi için tanımlama
for (int i = 0; i < 5; i++) // 0’dan 4’e kadar olan katsayılar için
conv += a[i] * x1[(xyPtr-i) & 4095]; // konvolüsyon girişleri
for (int i = 1; i < 5; i++) // 1’den 4’e kadar olan katsayılar için
conv += b[i] * y1[(xyPtr-i) & 4095]; // konvolüsyon çıkışları
y1[xyPtr] = conv; // filtre sonucu saklanır
y2[xyPtr] = (float)x1[xyPtr]; // orijinal olan saklanır
DAC->DHR12R1 = (int)y1[xyPtr]; // filtrelenmiş değer -> DAC
DAC->DHR12R2 = (int)y2[xyPtr]; // orijinal değer -> DAC
xyPtr = (xyPtr + 1) & 4095; //dairesel tampon işaretçisini bir artır
GPIOE->ODR &= ~0x0100; // PE08 low yap
}
Figür 3. IIR filtreleme uygulamasının program kodları

                Önemli işlemler kesme fonksiyonu içerisinde yapılır. Burada iki ADC’nin sonuçları önce dairesel tampona saklanır. Ardından bir giriş sinyali için iki konvolüsyon hesaplaması yapılır. Konvolüsyonun birinde mevcut ve geçmiş giriş örnekleri ve “am” katsayıları ve diğer konvolüsyon geçmiş filtreleme sonuçları ve “bn” katsayıları ile hesaplanır. Tamamlanan hesaplamada gerekli hassasiyeti sağlamak için kayan nokta aritmetiği kullanılarak yapılır. Çıkan sonuç dairesel tamponda saklanır. Filtre edilmemiş giriş sinyali karşılaştırma yapmak için başka bir çıkış tamponuna saklanır. Son olarak çıkış tamponlarındaki mevcut değerler DAC’a gönderilir ve dairesel tampon işaretçisi güncellenir.

                Tüm hesaplamalar basit olarak port E’de bulunan pin’in kesme fonksiyonu başlangıcında high yapılması ve hesaplama bitiminde yani fonksiyon çıkışında low yapılması ile geçen süre hesaplanır. Bu süre bu uygulamamızda yaklaşık 6uS civarındadır.
[1] Steven W.Smith: The Scientist and Engineer’s Guide to Digital Signal Processing

Şimdilik bu kadar arkadaşlar Gülen
Sorularınız ve önerileriniz için m.hakki.kaplan@gmail.com adresinden her zaman bana ulaşabilirsiniz.
Hakkı KAPLAN

25 Kasım 2015 Çarşamba

On-Line FIR filtre ve STM32F407



Figür 1’de gösterilen bir ADC, bir işlemci ve bir DAC bulunan uygulamada gerçek zamanlı olarak analog sinyallerin dijital filtrelemesidir. Bu yazımızda bir FIR filtre örneği verilecektir.

Figür 1. FIR filtrelemeye dâhil olan bloklar

                FIR (Finite Impulse Response) filtreleme giriş sinyalinin önceden belirlenmiş katsayılar ile konvolüsyon (convolution) kullanılarak yapılır. Konvolüsyon formülü aşağıda verilmiştir.


hm katsayısı filtrenin özelliklerini tanımlar ve istenilen filtrenin istenilen frekans karakteristiğinin Fourier dönüşümünün tersinin hesaplanmasıdır. “hm katsayıları “-M”den “+M”e (teoride “M sonsuza yakındır), “m indeksi negatif ve pozitif için bu metot kullanılarak hesaplanmıştır. Efektif olarak bunun anlamı; “k” zamanında filtre cevabının hesaplanması, “k” zamanındaki “x” örneklemesinin bilinmesi daha önceki örneklemeler (xk-1, xk-2, … , xk-m) ve gelecek örneklemeler ile hesaplanır. Biz uygulamada konvolüsyon formülünü direkt olarak kullanmayacağız fakat formülü değiştirerek veya yeniden düzenleyerek bir gecikme ile hesaplayacağız.



Bu durum grafiksel olarak figür 2’de gösterilmiştir. Örnek girişleri “x” çizimde kutu başına bir dairesel tampon (circular buffer) içerisinde saklanır. “k-1M”den “k” ya olan geçmiş indeksli örnekler konvolüsyon hesabı için kullanılır. Filtreden anlık çıkış olarak kullanılan “yk” sonucu aslında filtrelenmiş “x” giriş sinyalinin gecikme eklenmiş sonucudur. Gereksinim duyulan bu gecikme konvolüsyon hesaplamasında kullanılan katsayı sayısına bağlıdır.


Figür 2. FIR filtrelemede kullanılan örneklemeler

                “hm” katsayıları alçak geçiren (low pass) FIR filtre için aşağıdaki şekilde hesaplanır;



Burada “fs” örnekleme frekansı ve “fc” filtrenin köşe frekansıdır. Filtrenin katsayılarının fazla olması filtrenin keskinliği için en iyisidir. Ancak, konvolüsyon formülündeki birçok çarpma işleminde kullanılan katsayıların sayısının fazla olmasıdır bu durumda “m” seçilirken dikkatli olunmalıdır.

Köşe frekansları üzerindeki frekanslarda zayıflama düşük olabilir fakat “M” indeksine yakın katsayı değerlerinin azaltılması ile geliştirilebilir bu süreç “windowing” olarak da bilinir. Ortak pencere fonksiyonu yükseltilmiş kosinüs fonksiyonudur. Bu “von Hann window” olarak isimlendirilmiştir.



                FIR filtreleme gerçek zamanlı olarak uygulanabilir. Giriş sinyali düzenli aralıklarda örneklenmiş olmalıdır. Bu mikrodenetleyicinin bünyesinde bulunan bir timer ile elde edilebilir. Timer’ı başlatarak örneklemeyi başlatabilirsiniz ve daha önceki yazımızda anlattığımız “dairesel tampon” kullanarak alınan örnekleme değerleri “x” dairesel tamponunda saklanabilir. Kayıt edilmiş olan örnekleme değerleri yeni bir örneklemeden sonra konvolüsyon hesaplaması ile hemen hesaplanacaktır. Konvolüsyon sonucu bir DAC kullanılarak geri analog sinyale çevrilir veya “y” dairesel tampon içerisine kayıt edilir. Önemli olan nokta mevcut olan giriş sinyalinin yeni örneklemesinden sonra acil bir şekilde konvolüsyon hesaplamasının yapılmasıdır. Bu işlem kesme fonksiyonu içerisinde yapılır. Bir sonraki kesme isteğinden önce hesaplama bitirilmek zorundadır. Her çarpımda harcanan zamandan sonra konvolüsyon formülündeki katsayıların sayısı bunu limitleyecektir.

                Konvolüsyon formülünde kullanılan katsayı değerleri filtreleme boyunca aynı kalmalı ve filtreleme öncesinde bir defaya mahsus hesaplanması gerekmektedir.

                Figür 3’de bahsedilen program listelenmiştir. Programda ilk olarak 2 (iki) dairesel tampon “x1” ,“x2” ve “xPtr isimli işaretleyici tanımlanmıştır. Bu değişkenler diğer kesme fonksiyonlarında da kullanılacağından dolayı genel (global) olarak tanımlanmak zorundadır. Dairesel tampon uzunlukları abartılıdır fakat bu mikrodenetleyici için uzun tamponlar problem teşkil etmez. Tamponlar 32-bit integer olarak tanımlanmıştır. Bu durumda ADC’lerden gelen sonuçlar 16-bit olduğu için RAM tarafında tasarruf sağlanmış olacaktır. Daha sonra “w” ile isimlendirilen katsayı dizini tanımlanır ve veri tipi olarak “foat (kayan noktalı)” değer atanır. Katsayılar yukarıdaki formülü takiben az bir değere sahip olacağından dolayı floating yani kayan noktalı sayı olmak zorundadır. Bu dizin uzunluğunda abartılı olarak verilmiştir.

Programın “main” kısmında ilk olarak özelliklerin kurulumu gerçekleştirilir. “main” kısmında ADC ve DAC kurulumlarının fonksiyonları işlemin ana çatısının daha iyi anlaşılabilmesi için gizlenmiştir. Daha önceki ADC ve DAC ile ilgili yazılarımızda bu fonksiyonlar verilmişti. ADC 100uS zaman aralıklarında çevrime başlaması için timer bu çalışma şekline göre kurulumu yapılmıştır ve ADC’nin kesme istekleri aktif edilmiştir.

Ana program konvolüsyonun kullandığı katsayı değerlerini hesaplayarak devam eder. Yukarıdaki gördüğümüz formüldeki katsayı değerleri 0 (sıfır) indeksi ile merkezi katsayısı etrafında simetriktir, buna rağmen sadece pozitif “m” indeksleri için katsayıları hesaplamaya ihtiyacımız vardır ve sonraki 3(üç) program satırında bu işlem yapılmıştır. Pozitif indeksler ve indeks 0(sıfır) için bir merkez için 63 adet katsayı vardır.

Mikrodenetleyici filtreleme işlemini başlatmaya hazırdır. Program zaman gecikmesi ile beraber sonsuz döngü içerisinde devam eder.

#include "stm32f4xx.h"
#include "math.h"
#define pi 3.14159

int x1[4096], x2[4096], xPtr; // dairesel tamponların tanımlamaları
float w[1024]; // FIR ağırlıklarının tanımlaması

int main ()
{
GPIO_setup(); // pin kurulumlarının yapılması
DAC_setup(); // DAC kurulumunun yapılması
ADC_setup(); // ADC kurulumunun yapılması
Timer2_setup(); // Timer 2 kurulumunun yapılması
NVIC_EnableIRQ(ADC_IRQn); // NVIC ADC kesmesinin aktif edilmesi
w[0] = 2.0 * 100.0 / 10000.0;
for (short k = 1; k < 64; k++) // FIR ağırlıkları
w[k] = (w[0] * (sin(pi * k * w[0])) / (pi * k * w[0]));
for (short k = 1; k < 64; k++) // hanning window oluşturuluyor
w[k] = (w[k] * cos(pi/2 * k / 62.0));
// zaman gecikmesi
while (1)
{
if (GPIOE->IDR & 0x0001) GPIOA->ODR |= 0x0040; // LED yak
else GPIOA->ODR &= ~0x0040; // değil ise LED söndür
};
}
// kesme fonksiyonu
void ADC_IRQHandler(void) // geçiş yaklaşık 42us’dir
{
GPIOE->ODR |= 0x0100; // PE08 high yap
x1[xPtr] = ADC1->DR; // ADC1 sonucunu x1 dairesel tamponuna at
x2[xPtr] = ADC2->DR; // ADC2 sonucunu x2 dairesel tamponuna at
float conv = (float)x1[(xPtr - 100) & 4095] * w[0]; // merkezi ağırlığı al
for (int k = 1; k < 64; k++)
conv += w[k] * (x1[(xPtr - 100 + k) & 4095] + x1[(xPtr - 100 - k) & 4095]);
DAC->DHR12R1 = (int)conv; //sonucu DAC’a at
DAC->DHR12R2 = x1[(xPtr - 100) & 4095]; // orijinal değeri DAC’a at
xPtr = (xPtr + 1) & 4095; // dairesel tampon işaretçisini artır.
GPIOE->ODR &= ~0x0100; // PE08 low yap
}
Figür 3. FIR filtreleme uygulaması program kodları

Önemli işlemler kesme fonksiyonunda işletilir. ADC’den yeni bir örnekleme geldiğinde kesme fonksiyonu çağırılır. Her iki ADC’nin sonuçları “xPtr” işaretçisi kullanılarak dairesel tamponlarda saklanır. Sonra float değişken olan “w[0]” merkezi ağırlığı içerisinde ve önceki merkezi örnek “x1[(xPtr-100)&4095]” buraya saklanır. Burada biz anlık örneklemeden değerini 100 olarak vermeyi konvolüsyon uzunluğunu kapsamaya yeterli olduğunu varsayıyoruz. Bu değer “M” değerinde büyük olmak zorundadır.

Konvolüsyonun geri kalanı 63 dahil  “for” döngüsü içerisinde uygulanmaktadır. Katsayı değerleri merkezi katsayı etrafında simetriktir ve katsayının aynı değeri ile giriş örneklerinin çarpım bölümlerini oluşturmak için zaman gecikmesi gerekmektedir. İlk iki giriş örneklemesini eklemek ve sonra katsayı ile toplayıp çarpmak en iyisidir. Simetriyi vurgulamak ve AND fonksiyonu dairesel tamponu içerisinde işaretçiyi tutmak için “[xPtr-100+k]” ve “[xPtr-100-k]” girdi örnekleri olarak endekslenir.

İlk konvolüsyon hesaplanır, sonuç integer değere çevrilir ve DAC çevrimine gönderilir. Filtrelenmemiş orijinal sinyal ikinci DAC çevrimine karşılaştırma amaçlı olarak gönderilir. Bu sinyal filtrelenmiş olan sinyallin gecikmiş haliyle eşit olmak zorundadır. Konvolüsyonun kullandığı merkezi örnek DAC’a gönderilir.

İki GPIOE fonksiyonu port E üzerinde bir darbe sinyali oluşturmak için kullanılır. Darbe sinyali kesme fonksiyonunun işletilmesinde ne kadar zaman geçtiğini ölçmek için kullanılmaktadır (bu uygulamada 42uS).

Yürütme hızı integer tipte değişkenlerin ve integer matematik işlemlerinin kullanılmasıyla artırılabilir. Sadece figür 3’te gösterilen birkaç program satırını değiştirmek gerekebilir ve bunlar figür 4’de gösterilmiştir. Katsayılar için tanımlanmış olan dizi “int” olarak değiştirildi. Bundan önceki durumda katsayı değerleri 1 (bir) den küçüktü ve bu değerleri direkt olarak integer’a çevrilemezler. Fakat bu değerler 65536 ile çarpılabilir ve integer olarak saklanır (katsayı değerini 16-bit sola kaydırmak eşdeğerdir). Bundan sonra integer tipte katsayılara sahibiz ve integer tip giriş örneklerine tüm hesaplamalarda integer kullanabiliriz.  Beklendiği gibi bu 65536 kez daha büyük olan konvolüsyon sonucunu üretir ve 65536’ya bölünerek (16-bit sağa kaydırma ile) doğru sonuca ulaşılır.

int w[1024]; // FIR ağırlıklarının tanımlanması
// ana program içerisine
float w0 = 2.0 * 100.0 / 10000.0;
w[0] = (int)(w0 * 65536);
for (short k = 1; k < 64; k++) // FIR ağırlıkları
w[k] = (int)(w0 * 65536 * (sin(pi * k * w0)) / (pi * k * w0));
for (short k = 1; k < 64; k++) // Hanning window
w[k] = (int)((float)w[k] * cos(pi/2 * k / 63.0));
// kesme fonksiyonu içerisine
int conv = x1[(xPtr - 100) & 4095] * w[0]; // merkezi ağarlık al
for (int k = 1; k < 64; k++) // zaman gecikmesi
conv += w[k] * (x1[(xPtr - 100 + k) & 4095] + x1[(xPtr - 100 - k) & 4095]);
DAC->DHR12R1 = conv >> 16; // sonuç -> DAC
Figür 4. Integer filtreleme

Katsayıların hesaplanması ana program içerisinde değiştirildi. İlk olarak merkezi katsayı değeri “float” olarak hesaplandı fakat hemen integer tipteki değere çevrildi ve katsayı dizininin 0 (sıfır)ncı pozisyonuna saklandı. Sonra 1’den 63’e kadar indekslenen katsayı değerleri hesaplandı tümü 65536 ile çarpıldı. Hesaplanan katsayıları windowing yapmak amacıyla kayan nokta sayıları tekrar çevrilir, ağırlık ile çarpılır ve tekrar integer’a çevrilir.

Kesme fonksiyonu içerisinde ara değişken olan “conv” integer olarak tanımlanır ve hesaplama integer aritmetik belirtilmeden düzenli bir şekilde yapılır. Tün değerler integer olduğunda otomatik olarak bitmiş olur. Tek fark konvolüsyon sonucunun DAC’a gönderilmesi noktasındadır. O ilk 65536 değerine bölünür (16-bit sağa kaydırılır) ve ardından DAC’a yazılır.

Hızdaki önemli gelişme; kesme fonksiyonunun integer aritmetik kullanmasıyla birlikte sadece 10uS’dir.

FIR filtrelemede gerçekleştirilebilecek ilginç bir fonksiyon, 90 derece için bilinmeyen bir frekans ile bir sinyalin kaydırılmasıdır. Bu fonksiyon “Hilbert dönüşümü” olarak isimlendirilir. Hilbert dönüşümü için katsayılar şu formül ile hesaplanır.


FIR filtrelemenin integer sürümü için aynı program kullanılabilir fakat katsayılar yukarıdaki formül ile hesaplanmalıdır. Bu durum figür 5’de gösterilmiştir. Buradaki katsayılar 65535 ile çarpılır ve konvolüsyon sonucu aynı faktöre bölünmelidir.

// ana program içerisinde
w[0] = 0;
for (short k = 1; k < 64; k++) // FIR ağırlıkları
w[k] = (int)(65536.0 * (-1.0 + cos(pi * k)) / (pi * k));
for (short k = 1; k < 64; k++) // Hanning window
w[k] = (int)((float)w[k] * cos(pi/2 * k / 63.0));
Figür 5. Hilbert dönüşümü ile katsayıların hesaplanması

Figür 6’da özelliklerin kurulumu ile ilgili fonksiyonlar gösterilmiştir. Figür 3’de bunlar ana program dışına atılmıştı.

void ADC_setup(void)
{
RCC->APB2ENR |= 0x00000100; // ADC1 için clock
RCC->APB2ENR |= 0x00000200; // ADC2 için clock
ADC->CCR = 0x00000006; // Regular simultaneous mode
ADC1->CR2 = 0x00000001; // ADC1 açık
ADC1->SQR3 = 0x00000002; // PA02 giriş olarak kullan
ADC2->CR2 = 0x00000001; // ADC2 açık
ADC2->SQR3 = 0x00000003; // PA03 giriş olarak kullan
GPIOA->MODER |= 0x000000f0; // PA02, PA03 analog giriş yap
ADC1->CR2 |= 0x06000000; // TIM2, TRG0’da SC kaynağını kullan
ADC1->CR2 |= 0x10000000; // harici SC kaynağı, yükselen kenar
ADC1->CR1 |= 0x00000020; // EOC için ADC kesmesi aktif
}

void DAC_setup(void)
{
RCC->APB1ENR |= 0x20000000; // DAC için clock aktif
DAC->CR |= 0x00010001; // DAC kontrol register, her iki kanalda açık
GPIOA->MODER |= 0x00000f00; // PA04, PA05 analog çıkışlar
}

void GPIO_setup(void)
{
RCC->AHB1ENR |= 0x00000001; // GPIOA için clock aktif
RCC->AHB1ENR |= 0x00000010; // GPIOE için clock aktif
GPIOE->MODER |= 0x00010000; // PE08 pin’i çıkış: zaman izleme için
GPIOE->MODER |= 0x00040000; // PE09 pin’i çıkış: toggle
GPIOA->MODER |= 0x00001000; // PA06 pin’i çıkış: LED
}

void Timer2_setup(void)
{
RCC->APB1ENR |= 0x0001; // Timer 2 için clock aktif
TIM2->ARR = 8400; // otomatik geri yükleme değeri: 8400 == 100us
TIM2->CR2 |= 0x0020; // TRGO’ı güncelleme olayı olarak seç (UE)
TIM2->CR1 |= 0x0001; // sayacı başlat
}
Figür 6. Kurulum fonksiyonları listesi

Şimdilik bu kadar arkadaşlar Gülen
Sorularınız ve önerileriniz için m.hakki.kaplan@gmail.com adresinden her zaman bana ulaşabilirsiniz.
Hakkı KAPLAN

22 Kasım 2015 Pazar

Dairesel Tampon (Circular Buffer) Kullanımı


Dairesel bir tampon veri için geçici tampon görevi yapan ayrılmış bir hafıza parçasıdır.  Özel bir şekilde düzenlenmiştir; gelen veri tamponu tampon tam dolana kadar doldurur ve daha sonra gelen veriler tekrar tamponu eski verilerin üzerine yazarak doldurmaya devam eder. Yeni veriler eski verilerin üzerine yazılmadan eski verileri kullanmak yazılımın görevidir.

Genelde bir dizi (array) ve bu dizi içerisinde bir işaretleyici (pointer) kullanarak dairesel tampon oluşturulur. Ancak işaretleyici dizine bağlı olmalı ve dizi dışında bir yeri göstermemelidir. Bu durum programın çökmesine neden olabilir. İşaretçi dizin dışında bir alana gelen veriyi yazar ise bu başka fonksiyonların kullandığı bir hafıza alanındaki değeri veya sistem hafıza alanında bir yerdeki veriyi bozacaktır. Bunun önlenmesi gerekmektedir.

Diyelim ki 2 üzeri N uzunluğunda bir dizi kullanmaya karar verdik. Burada ki N doğal bir sayıdır. “N” 3 (üç)’e eşit olsun. Biz 8 elemanlı bir dizi ile ilgilenmiş oluruz. Bu dizinin indeks değer aralığı 0 ile 7 arasında desimal veya “000” ile “111” arasında binary’dır.  Biz binary olarak yazılmış Ptr olarak isimlendirilmiş bir integer sayı alır isek ve sadece en alt üç bit’ini işaretleyicide kullanır isek bu üç bit noktası dizi içeresinde birer elemana karşılık gelecektir. Bu nedenle 8-bit bir işaretçinin sadece en alt 3(üç) bit’ini kullanıp diğer bit’leri AND işlemine tabi tutarak işaretçiyi hep dizi adresi içerisinde tutabiliriz. Böylece “111” son kullanılabilir indeks değeri olur.

Şimdi Ptr sayısını 0 (sıfır)’dan başlayarak 1(bir) artırıldığını ve Ptr’nin dizi içerisinde işaretçi olarak kullanıldığını düşünelim. Ptr’nin değeri maksimum 7(yedi) olacak şekilde AND işlemine tabi tutulur ise Ptr her artırıldığında dizinin eleman 0, eleman 1, eleman 2 ve eleman 7 ve tekrardan eleman 0’a döndüğü görülecektir.

Düşünün Ptr değeri 2(iki)’den başlayarak 1(bir) azaltılsın ve daha kolay anlayabilmemiz için 8-bit binary kullanalım. Azaltılan sayılar 00000010b den 00000001b ve daha sonra 00000000b olur. Devam edilir ise 11111111b, 11111110b ve 11111101b vb. devam eder. İşaretli integer sayıların 2(iki)’nin tamlayanı olarak yazıldığını hatırlayın. 11111111b işaretli tam sayı olarak -1’e denk gelir ve 11111110b ise -2’ye denk gelir. Ptr’nin en düşük 3(üç) bit’ini dizi sınırlarının içinde tekrar koruyalım ve 2, 1, 0 ,7, 6 gibi Ptr değeri almaya çalışalım. Yani 0(sıfır)’dan azaltma yaptığımızda 11111111b yerine 7 elemanına ulaşalım. Bu şekilde kullanılan bir dizi dairesel tampon olarak adlandırılır.

Hüner basit AND lojik fonksiyonuna dayanır ve 2 üzeri N elemanlı bir dairesel tampon için etkilidir. Diğer uzunluklarda işaretleyici için karmaşık bir sınırlayıcı gerekir ve en iyisi bu durumdan kaçınmaktır.

Biz bu uygulamada DAC sinyalinin üretilmesini geciktirmek için bir dairesel tampon kullandık. Bu programda donanım kurulumlarını önceki yazılarımızda anlattığımız için burada tekrar anlatılmayacaktır. Giriş sinyalini periyodik olarak örnekleyen bir ADC kullanılmıştır(gerçekte her iki ADC’de kurulur ve çalıştırılır, fakat sadece birinin sonucu kullanılmıştır). Örneklenen veriler “Result” ismi verilen bir dairesel tamponda kayıt edilmektedir. 2(iki) DAC’tan bir ADC sonucuna direkt bağlıdır. Diğer DAC, ADC sonucunun 100 örnek öncesini verecektir. Tam kod listesi Figür 1’de verilmiştir.

#include "stm32f4xx.h"

int Result[1024], Ptr = 0;

void main () {
// GPIO clock aktif etme, dijital pin tanımlamaları
RCC->AHB1ENR |= 0x00000001; // GPIOA clock aktif edilir.
RCC->AHB1ENR |= 0x00000010; // GPIOE clock aktif edilir.
GPIOE->MODER |= 0x00010000; // PE08 çıkış pin’i: zaman işareti
GPIOA->MODER |= 0x00001000; // PA06 çıkış pin’i: LED
// DAC kurulumu
RCC->APB1ENR |= 0x20000000; // DAC clock aktif et.
DAC->CR |= 0x00010001; // DAC kontrol register, her iki kanal açık
GPIOA->MODER |= 0x00000f00; // PA04, PA05 analog çıkışlar
// ADC kurulumu
RCC->APB2ENR |= 0x00000100; // ADC1 için clock
RCC->APB2ENR |= 0x00000200; // ADC2 için clock
ADC->CCR = 0x00000006; // sadece eş zamanlı mod
ADC1->CR2 = 0x00000001; // ADC1 açık
ADC1->SQR3 = 0x00000002; // PA02’yi giriş olarak kullan
ADC2->CR2 = 0x00000001; // ADC1 açık
ADC2->SQR3 = 0x00000003; // PA03’ü giriş olarak kullan
GPIOA->MODER |= 0x000000f0; // PA02, PA03 analog girişler
ADC1->CR2 |= 0x06000000; // TIM2, TRG0 SC kaynağını kullan
ADC1->CR2 |= 0x10000000; // harici SC aktif, yükselen kenar
ADC1->CR1 |= 0x00000020; // EOC için ADC kesmesi aktif
// NVIC kesmesi aktif
NVIC_EnableIRQ(ADC_IRQn); // ADC için NVIC kesmesi aktif
// Timer 2 kurulumu
RCC->APB1ENR |= 0x0001; // Timer 2 için clock aktif
TIM2->ARR = 8400; // otomatik geri yükleme değeri: 8400 == 100us
TIM2->CR2 |= 0x0020; // TRGO güncelleme olayı için TRG0 seçili(UE)
TIM2->CR1 |= 0x0001; // sayıcı aktif
// zaman gecikmesi
while (1) {
if (GPIOE->IDR & 0x0001) GPIOA->ODR |= 0x0040; // LED açık
else GPIOA->ODR &= ~0x0040; // else LED kapalı
};
}
// kesme fonksiyonu
void ADC_IRQHandler(void) //geçiş süresi yaklaşık 400ns!
{
GPIOE->ODR |= 0x0100; // PE08 high yap
Ptr = (Ptr + 1) & 1023; // işaretçiyi artır ve değeri limitle
Result[Ptr] = ADC1->DR; // ADC değerini dairesel tamponda sakla ve EOC bayrağını temizle
DAC->DHR12R1 = Result[Ptr]; // anlık sonucu DAC’a at.
DAC->DHR12R2 = Result[(Ptr - 100) & 1023]; // önceki sonucu DAC’a at.
GPIOE->ODR &= ~0x0100; // PE08 low yap
}

Şimdilik bu kadar arkadaşlar Gülen
Sorularınız ve önerileriniz için m.hakki.kaplan@gmail.com adresinden her zaman bana ulaşabilirsiniz.
Hakkı KAPLAN

20 Kasım 2015 Cuma


Kesmeler ve Timer TIM5

Daha önceki yazılarımızda kesme işlemini anlatmıştık (Kesmeler ve Portlar yazısı). Bu uygulamada Timer ve kesme işlemini kullanarak işlemcinin kesin olarak tanımlanmış zaman aralıklarında bazı yararlı işler yapması sağlanmıştır. Bu durumda işlemci port E’nin bit-3’ünde her kesme isteğinde 2 (iki) ardışık darbe sinyali oluşturur.


Mikrodenetleyicinin bu uygulamadaki davranışları basit bir şekilde Figür 1’de gösterilmiştir.

Figür 1. Periyodik kesme istekleri uygulamasında kullanılan basitleştirilmiş yapı

Bu uygulamada TIM5 Timer’ı kullanılmıştır. TIM2 Timer önceki yazılarımızda anlatılmıştı. TIM5 Timer kullanımına TIM2 Timer kullanımına benzer. TIM5 Timer 32-bit sayıcı ve sayma için kontrol ve giriş sinyali seçimi için ek donanıma sahiptir. TIM5 Timer mikrodenetleyici içerisindeki 84MHz frekansı ile sürülür. Bu sayıcı için çok hızlı bir clock’tur. Varsayılan olarak Multiplexer daha önce tanımlanan multiplexer’lar ile bu clock sayıcı girişine bağlanmıştır.

                TIM5 Timer önceden tanımlanmış değerden yukarı doğru sayar ve sıfır olduğunda reset’lenir. Sayaç değeri sıfıra döndüğünde güncelleme olayı çağırılır. Ön değer TIM5_ARR(Auto Reload Register) register’ı içerisinde tutulur. TIM5_ARR register değeri sıfır ise o zaman sayıcı 4.294.967.295’e kadar sayar.  TIM5_ARR register’ı 84000 olarak ayarlanır ve clock 84MHz kullanılarak figür 1’de görüldüğü gibi T zaman aralıklı sinyaller 1ms olacak şekilde üretilir. Bu güncelleme kesme isteği kullanılarak yapılabilir.

                Güncelleme olayları NVIC denetleyicisinin girişi için kesme istekleri konumlandırılır. TIM5 Timer’ından gelen kesme isteği için kanal aktif edilmelidir. Bu “NVIC_EnableIRQ” fonksiyonu çağırılarak NVIC denetleyici aktif edilerek yapılır.

                Kesme fonksiyonu kesme isteği ile çalıştırılır. Sinyal oluşturmak için işletilecek olan komutlar Kesme fonksiyonu içinde yapılmalıdır. Bu durumda 4 duruma ihtiyaç vardır; port E pin 8 high-low-high-low yapılır. Ek olarak güncelleme olayı TIM5_SR (Status Register) bit-0’ da tutulur. Bu bit aynı kesme isteğinin tekrarlayabilmesi için kesme fonksiyonu içerisinde temizlenmelidir.

Ve olaylar şu şekilde devam eder. TIM5 sayacı önceden belirlenmiş değere geldiğinde TIM5_ARR register’ında tekrar yüklenir, TIM5_CNT sayaç register’ının içeriği sıfıra döndüğünde olaylar tetiklenir ve ardından sayaç sıfırdan saymaya başlar. Tekrar yüklenen olaylar TIM5_SR register’ı bit-0’ına kayıt edilir ve eş zamanlı olarak TIM5 Timer üzerinden NVIC denetleyicisinde kesme isteğine izin verilir. Daha sonra NVIC denetleyici bu parça kesme isteğine cevap vermek için aktif edilir.  Normal program akışında işlemci bununla kesme işlemini yapmaya zorlanır. Ve kesme fonksiyonu çalıştırılır. Kesme fonksiyonu içerisinde işlemci port E pin bit-3’ünü 2 kez ardışık darbe sinyali oluşacak şekilde değiştirir ve kesme fonksiyonunun çalışmasını kabul etmek için güncelleme olayı kaydının tutulduğu TIM5_SR register’ının bit-0’ını temizler. Daha sonra işlemci normal programa devam eder.
Uygulama kodları figür 2’de verilmiştir. Program port A, port E ve TIM5 Timer clock’larının aktif edilmesi ile başlar. Port E’deki pin çıkış olarak ayarlanır darbe sinyalleri burada oluşacaktır ve port A’nı pin’i çıkış olarak ayarlanır. Burada bir adet LED bağlıdır. Butona basılı tutulduğunda LED yanmakta, buton bırakıldığın LED sönmektedir. Bu işlem normal program içerisinde yapılmaktadır.

#include "stm32f4xx.h"
int main () {
RCC->AHB1ENR |= 0x0010; // GPIOE ve GPIOA clock aktif edilir.
RCC->APB1ENR |= 0x00000008; // Timer 5 clock aktif edilir.
GPIOE->MODER |= 0x00010000; // port E pin’i çıkış olarak ayarlanır.
GPIOA->MODER |= 0x00001000; // LED’in bağlı olduğu pin çıkış olarak ayarlanır.
NVIC_EnableIRQ(TIM5_IRQn); // TIM5 kesmesi aktif edilir.
TIM5->ARR = 84000; // Auto Reload Register değeri => 1ms
TIM5->DIER |= 0x0001; // DMA/IRQ Enable Register – kesme güncellemesi aktif edilir.
TIM5->CR1 |= 0x0001; // sayma işlemi başlatılır
while (1) {
if (GPIOE->IDR & 0x01) GPIOA->ODR |= 0x0040; // butona basıldı ise LED’i yak.
else GPIOA->ODR &= ~0x0040; // LED’i söndür.
};
}

void TIM5_IRQHandler(void)
{
TIM5->SR &= ~TIM_SR_UIF; // TIM5 kesme bayrağını temizle
GPIOE->ODR |= 0x0100; // PE08 yukarı (high)
GPIOE->ODR &= ~0x0100; // PE08 aşağı (low)
GPIOE->ODR |= 0x0100; // PE08 yukarı (high)
GPIOE->ODR &= ~0x0100; // PE08 aşağı (low)
}
Figür 2. Port E bit-3 kesme isteği kurulumu

                Kesme denetleyicisi “NVIC_EnableIRQ” fonksiyonunun TIM5 timer tarafından çağrılmasıyla aktif edilir. Kurulum TIM5 timer ayarlarının yapılması ile sona erer. Geri yükleme değeri TIM5_ARR register’ı içerisine yazılır ve güncelleme olayı TIM5_DIER register’ının LS bit’inin ayarlanması ile kesme isteği tarafından konumlandırılır. Sonuç olarak timer TIM5_CR kontrol register’ının LS bit’ini set ederek çalıştırmak için aktif eder.
Ana döngü içerisinde buton kontrol edilir. Butona basılı tutulduğu sürece LED yanar durumda kalır.
Kesme fonksiyonu kesme vektör tablosunda ki isme göre isimlendirilir(TIM5_IRQHandler). Fonksiyon herhangi bir değişken içermez, tanımlamada “void” kelimesi kullanılır. Fonksiyon içerisinde ilk olarak güncelleme olayı bayrağı temizlenir ve daha sonra 2(iki) darbe sinyali üretilir.

Şimdilik bu kadar arkadaşlar Gülen
Sorularınız ve önerileriniz için m.hakki.kaplan@gmail.com adresinden her zaman bana ulaşabilirsiniz.
Hakkı KAPLAN