1 Aralık 2015 Salı


STM32F407’de PID Denetleyicisi Uygulaması


                PID (Proportional Integral Differential – Oransal İntegral Diferansiyel) denetleyicisi düzenlemede temel yapı taşıdır. Bu birçok farklı şekilde uygulanabilir. Bu örnekte mikrodenetleyicide nasıl kod yazılır ve yeteneklerin basit bir gösterimi yapılmıştır.
               

İyi karıştırılırmış bir su kabı düşünün (sistem) ki bu sistemin çevre sıcaklığının üstünde bir sıcaklıkta kalması gerekmektedir (referans değer “R” olsun). Bizim yapmamız gereken su içerisine bir termometre (sensor) koymak ve bununla sıcaklığı okumaktır ( anlık değer “X” olsun). Su çok soğuk ise kabın altına yerleştirilmiş olan ısıtıcı (aktuator) çalıştırılsın. Termometre okunarak termometre değeri istenilen değere geldiğinde ısıtıcı kapatılsın. Suyun sıcaklığı hala biraz zaman (aşmayı) için yükselir ve daha sonra azalmaya başlar. Suyun sıcaklığı istenilen değerin altına düştüğünde, biz ısıtıcıyı tekrar açalım. Isıtıcı ısınır ve suyu ısıtmaya başlar ama suyun sıcaklığı yine istenilen değere ulaştığında işlem tekrarlanır. Biz bir düzenleme sistemine sahibiz, burada denetleyicimiz; gerçek değeri gözlemler referans değer ile karşılaştırırız ve karşılaştırmanın sonucunda sistemi etkileriz. Bu durum basit bir şekilde figür 1’de gösterilmiştir.

Figür 1. Düzenlemenin ham ve basitleştirilmiş hali

                Yukarıdaki örnekte su sıcaklığı hiçbir zaman istenen değerde kalmaz, ancak bunun yerine çevresinde salınımlar oluşur. Salınımlar sistemin “F”, algılayıcı ve ısıtıcı özelliklerine bağlıdır. Sıcaklık davranışını iyileştirmek ve salınımı azaltmak için denetleyicide daha karmaşık kararlar tanımlayarak düzenleme sürecini geliştirebiliriz. Örneğin aşma miktarını biliyorsak sıcaklığı istenilen değere gelmeden önce durdurabiliriz.  Biz gerçek sıcaklığı istenilen sıcaklığa yakın olduğunda suyu ısıtan sıcaklık miktarını azaltarak da bu bahsi geçen aşmayı azaltabiliriz. Başka olasılıklarda vardır. Ama bunlar kontrol ünitesinde ki tanımlamalar ile hayata geçirilebilirler. Bu duruma PID düzenleme adı verilir.

                Yukarıdaki örnek açısından düzenleme teorisinin bileşenleri ikinci dereceden diferansiyel eşitlikle açıklanabilir ve düzenlenmiş sistem ikinci dereceden olarak isimlendirilir. Bunlar PID denetleyici tarafından en iyi şekilde evcilleştirilmiştir.


Figür 2. PID denetleyici ile kontrol edilen sistem

                PID denetleyicisinde ilk istenilen değer ve anlık gerçek diğer karşılaştırılarak aralarındaki fark hesaplanır. Hesaplanan hata sinyali oransal (Proportional),integral (Integral) ve farksal bölüme (Differential) ayrılır. Bu üç bileşen ilgili faktörler (Kp, Kd, Ki) ile toplanarak final değer elde edilir. Bu final değer (Reg) ısıtıcı tarafından kullanılan değerdir.

                Böyle bir PID denetleyicisi mikrodenetleyicide periyodik olarak uygulanmalıdır. Yukarıdaki eylemlerin yapılma zamanı düzenlenmiş sistemin cevap zamanını karşılamalıdır. Yani eylem zamanı yeteri kadar kısa olmalıdır. Periyodik örnekleme, hesaplama ve değerin üretilmesi için bu tekrar çağırılmalıdır. Aynı programlama çatısı daha önceki yazılarımızda FIR ve IIR filtrelemede kullanılmıştır. Mikrodenetleyici kurulumu aynıdır. Tüm denetleyici hesaplama fonksiyonları kesme fonksiyonu içerisindedir. Programın listesi figür 3’de verilmiştir.

                Program değişkenlerin tanımlamaları ile başlar. İki integer tipte dairesel tampon istenen ve gerçek değer için kullanılmıştır.  Ek olarak iki floating tipte işaretçi dairesel tampon (floating point circular buffers) hata ve bir önceki çıkış değeri için kullanılmıştır. Bileşenler için üç değişken ve ilgili ağırlık faktörleri için üç floating point sayı ve kurulum tanımlanmıştır. Sonuç olarak değişim oranı hesaplaması için gerekli bir değişken tanımlanmış ve kurulumu yapılmıştır.

                Ana program içerisinde ADC ve DAC kurulumları, örnekleme frekansı 10kHz olan her 100us ADC çevrimini başlatan timer programlanmıştır. NVIC kesme denetleyici ADC kesme isteğini aktif hale getirmiştir. Daha sonra mikrodenetleyici sonsuz döngüye girer. PID düzenleyicisi program yürütülürken oransal (Proportional), integral (integral) ve diferansiyel (Differential) bileşenler için ağırlıkların değişimine izin verilir. Durum butonu periyodik olarak kontrol edilir, zaman gecikmesi sonsuz döngü başında döngü tarafından tanımlanmaktadır. Sonraki 3 (üç) program satırı oransal bileşen (Proportional component) ağırlık değerini değiştirmek için kullanılır.  Buton S370( port E’ bit 0’a bağlı olan buton) buton S375 (port E bit 5’e bağlı olan buton)  ile birlikte basıldığında, o zaman ağırlık bir artırılır. Buton S370 buton S374 birlikte basıldığında ise (port E, bit 4, 0x10) daha sonra ağırlık bir azaltılır. Üçüncü satır 0 ile 1000 arasında değerler (dâhil) ile orantılı ağırlık değerini sınırlar. Sonraki üç satır diferansiyel bileşen için ağırlıklarla aynı işi yapar ve sonraki üç satır integral bileşen için aynı işi yapar. Sonuç olarak bu üç ağırlık LCD ekrana yazdırılır.

#include "stm32f4xx.h"
#include "LCD2x16.c"

int Ref[64], x[64], Ptr; // dairesel tamponların tanımlanması
int Error[64], Reg[64]; // hata ve önceki çıkış vektörlerinin tanımlanması
float Prop, Dif, Int = 0; // üç bileşenin tanımlamaları
float Kp = 1.0, Ki = 0.0, Kd = 0.0; // kurulum parametrelerinin tanımlamaları
float Ts = 0.0001; // TIM2’nin 8400 sabit değeri için 10kHz’e ayarlanması

int main ()
{
GPIO_setup(); // GPIO kurulumu
DAC_setup(); // DAC kurulumu
ADC_setup(); // ADC kurulumu
Timer2_setup(); // Timer 2 kurulumu
NVIC_EnableIRQ(ADC_IRQn); // NVIC ADC kesme isteğinin aktif edilmesi
LCD_init();
LCD_string("Kp:", 0x00); LCD_string("Kd:", 0x09); LCD_string("Ki:", 0x49);


// genlik ve zaman gecikmesinin ayarlanması – belirsiz döngü
while (1)
{
for (int i = 0; i < 2000000; i++) {}; // zaman gecikmesi
if ((GPIOE->IDR & 0x003f) == (0x01 + 0x20)) Kp++; // Kp’nin manuel ayarlanması
if ((GPIOE->IDR & 0x003f) == (0x01 + 0x10)) Kp--;
if (Kp<0) Kp = 0; if (Kp > 1000) Kp = 1000;
if ((GPIOE->IDR & 0x003f) == (0x02 + 0x20)) Kd += 0.001; // Kd’nin manuel ayarlanması
if ((GPIOE->IDR & 0x003f) == (0x02 + 0x10)) Kd -= 0.001;
if (Kd < 0) Kd = 0; if (Kd > 1) Kd = 1;
if ((GPIOE->IDR & 0x003f) == (0x04 + 0x20)) Ki += 0.0001; // Ki’nin manuel ayarlanması
if ((GPIOE->IDR & 0x003f) == (0x04 + 0x10)) Ki -= 0.0001;
if (Ki < 0) Ki = 0; if (Ki > 1) Ki = 1;
LCD_sInt3DG((int)Kp,0x03,1); // Kp’yi LCD ekrana yaz
LCD_sInt3DG((int)(Kd*1000),0x0c,1); // Kd’yi LCD ekrana yaz
LCD_sInt3DG((int)(Ki*10000),0x4c,1); // Ki’yi LCD ekrana yaz
};
}


// kesme fonksiyonu
void ADC_IRQHandler(void) // yaklaşık 6us sürer
{
GPIOE->ODR |= 0x0100; // PE08 high yap
Ref[Ptr] = ADC1->DR; // ADC -> dairesel tampon x1
x[Ptr] = ADC2->DR; // ADC -> dairesel tampon x2
// PID hesaplamayı başlat
Error[Ptr] = Ref[Ptr] - x[Ptr]; // hatayı hesapla
Prop = Kp * (float)Error[Ptr]; // oransal kısım (Proportional)
Dif = Kd * (float)(Error[Ptr] - Error[(Ptr-1) & 63]) / Ts; // diferansiyel kısım
Int += Ki * (float)Error[Ptr]; // integral kısmı
Reg[Ptr] = (int)(Prop + Dif + Int); // üçünü topla
// PID hesaplamasını durdur
if (Reg[Ptr] > 4095) DAC->DHR12R1 = 4095; // DAC çıkış limiti
else if (Reg[Ptr] < 0) DAC->DHR12R1 = 0;
else DAC->DHR12R1 = (int)Reg[Ptr]; // düzenleyici çıkışı-> DAC
DAC->DHR12R2 = Error[Ptr] + 2048; // hata -> DAC
Ptr = (Ptr + 1) & 63; // dairesel tampon işaretçisini artır
GPIOE->ODR &= ~0x0100; // PE08 low yap
}
Figür 3. PID denetleyici uygulaması program listesi


                Tüm hesaplamalar kesme fonksiyonu içerisinde gerçekleşir. İki ADC’nin çevrim sonuçları önce dairesel tampona saklanır. Bu iki değerin arasındaki fark hesaplanır. Sonra acilen üçüncü dairesel tampona hata olarak saklanır. Sonraki üç satırda üç bileşen ve ağırlık hesaplanır. Dördüncü satırda diğer bileşenler eklenir ve dördüncü dairesel tampon olan “Reg” içerisine toplanarak saklanır. Bu değer düzenleyici çıkışı olacaktır ve DAC’a gönderilecektir. Fakat DAC 0 ile 4095 arasındaki değerleri kabul edebilir, bu durumda diğer sayılarda bu aralık içerisine katılır; örneğin 4097 ile DAC’da 1 sayısı aynıdır ve bu düzenleyicide önemli hataya neden olur. Sayılar kabul edilebilir değerler için en iyi şekilde sınırlandırılır ve bu sonraki üç kod satırında yapılır. İkinci DAC hata sinyalinin analog sürümünü üretmek için kullanılır ve değer hata değerine 2048 ekleyerek DAC aralığının yarısı için kaydırılır. Sonunda dairesel tampon işaretçisi güncellenir.

Figür 4. Uygulama için bağlantı

                Kesme fonksiyonu içerisinde başlangıçta ve bitişte port e bit-0 high ve low yapılarak fonksiyon içerisindeki harcanan zaman ölçülür. Bu yaklaşık olarak 6us’dir.

                Program DAC çıkışı (Reg, DAC1) ve ADC girişi (gerçek değer, ADC2-> DR, ADC3 giriş) arasında basit ikinci derece sistemini ekleyerek test edilebilir. Diğer ADC (ADC->DR, ADC2 girişi) istenilen değerin okunması için kullanılır. İki seri bağlanmış RC devreleri ikinci dereceden sistem için bir yedek olarak kullanılabilir; Bu gösterimi kolaylaştırır. Ek olarak istenilen değer olan “Ref” ve parazit sinyali olan “Intf” bir fonksiyon jeneratörü kullanılarak üretilebilir. Komple örnek devrenin bağlantıları figür 4’de verilmiştir.

                Figür 5’den 8’e figür 4’deki devre için gerçek değerler (X, kırmızı), fark değerleri Kp, Kd ve Ki için verilmiştir. Parazit sinyali “Intf” sıfır olarak tutulur ve istenilen değer (Ref mavi) 10Hz frekansında kare dalgadır. “Ref” sinyalinin ofseti ADC göstergesinin ortasına yakın ayarlanır. Yatay gösterge çizelgesi saniyeleri, dikey gösterge çizelgesi volt değerini verir.

Figür 5. Kp=1, Kd=0,Ki=0; oransal kazanç çok düşük ve gerçek değer(kırmızı) istenilen değere ulaşmıyor.


Figür 6. Kp=50, Kd=0,Ki=0; oransal kazanç yüksek, gerçek değer(kırmızı) istenilen değere(mavi) yakın, fakat salınımlar görünür halde


Figür 7. Kp=50, Kd=40, Ki=0; diferansiyel kazanç salınımları düzeltir fakat gerçek değer(kırmızı) hala istenilen değer(mavi) ile aynı değil.

Figür 8. Kp=50, Kd=40, Ki=40; integral kazancı istenilen değerin ortalamasını gerçek değerin ortalamasına doğru iter

                Sonraki diyagramlar figür 9’dan figür 12’ye kadar olan diyagramlarda düzenlenmiş sistem cevapları (kırmızı) parazit sinyali (mavi) olarak verilmiştir. Referans sinyali (gösterilmemiştir) 1.21V değerinde sabit tutulmuştur. Bu düzenleyici işini düzgün yaparsa yanıtın sabit olduğu tahmin edilmiştir.

Figür 9. Kp=1, Kd=0, Ki=0; oransal kazanç çok düşük ve girişim yapan sinyalin etkisi önemli

Figür 10. Kp=50, Kd=0, Ki=0; oransal kazanç yüksek, gerçek değer (kırmızı) 1.21V’ta yakın lakin girişim sinyalinin neden olduğu salınımlar görünür halde

Figür 11. Kp=50, Kd=40, Ki=0; diferansiyel kazanç salınımı düzeltmiş lakin gerçek değer(kırmızı) istenilen değer (1.21V) ile aynı değil

Figür 12. Kp=50, Kd=40, Ki=40; integral kazanç istenilen değer (1.21V) ile aynı olması için gerçek değerin ortalamasını alır.

Ş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



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