Hızlı ve hafıza verimli hareketli ortalama hesaplama


33

C cinsinden hareketli bir ortalamayı hesaplamak için zaman ve hafıza açısından verimli bir çözüm arıyorum. Bölünmekten kaçınmam gerekiyor çünkü özel bir bölüm birimi olmayan bir PIC 16'dayım.

Şu anda, tüm değerleri yalnızca bir arabellekte saklıyorum ve her yeni bir değer geldiğinde toplamı depolayıp güncelleştiriyorum. Bu gerçekten verimli ama maalesef elimdeki hafızanın çoğunu kullanıyor ...


3
Bunu yapmanın daha verimli bir yolu olduğunu sanmıyorum.
Rocketmagnet

4
@JobyTaffey, kontrol sistemlerinde oldukça yaygın kullanılan bir algoritmadır ve sınırlı donanım kaynaklarıyla uğraşmayı gerektirir. Bu yüzden burada SO'dan daha fazla yardım bulacağını düşünüyorum.
clabacchio

3
@Joby: Bu konuda, kaynakların sınırlı olduğu küçük sistemlerle ilgili bazı kırışıklıklar var. Cevabımı gör. Bunu, SO milletlerinin alıştığı büyük bir sistemde çok farklı şekilde yaparsınız. Bu benim elektronik tasarımı deneyimimde çok arttı.
Olin Lathrop

1
Katılıyorum. Bu, gömülü sistemler için geçerli olduğundan, bu forum için oldukça uygundur.
Rocketmagnet

Yanıtlar:


55

Diğerlerinin de belirttiği gibi, şu anda kullandığınız FIR (sonlu dürtü yanıtı) filtresinden ziyade bir IIR (sonsuz dürtü yanıtı) filtresi düşünmelisiniz. Dahası var, ama ilk bakışta FIR filtreleri açık dönüşümler ve denklemli IIR filtreleri olarak uygulandı.

Mikrodenetleyicilerde çok kullandığım IIR filtresi tek kutuplu düşük geçişli bir filtredir. Bu, basit bir RC analog filtrenin dijital eşdeğeridir. Çoğu uygulama için, bunlar kullandığınız kutu filtresinden daha iyi özelliklere sahip olacaktır. Karşılaştığım bir kutu filtresinin çoğu, dijital sinyal işleme sınıfında özel özelliklerine ihtiyaç duyulmasının bir sonucu olarak dikkat etmeyen birinin sonucudur. Yalnızca gürültü olduğunu bildiğiniz yüksek frekansları azaltmak istiyorsanız, tek kutuplu düşük geçiş filtresi daha iyidir. Mikrodenetleyicide birini dijital olarak uygulamanın en iyi yolu genellikle:

FİLT <- FİLT + FF (YENİ - FİLT)

FILT kalıcı bir durumdur. Bu, bu filtreyi hesaplamanız için gereken tek değişkendir. YENİ, filtrenin bu yinelemeyle güncellenmekte olduğu yeni değerdir. FF, filtrenin "ağırlığını" ayarlayan filtre kısmıdır. Bu algoritmaya bakın ve FF = 0 için çıkışın hiç değişmediğinden filtrenin sonsuz ağır olduğunu görün. FF = 1 için, çıktı sadece girişi takip ettiğinden dolayı aslında hiçbir filtre yoktur. Faydalı değerler arasındadır. Küçük sistemlerde FF'yi 1/2 N olacak şekilde seçersiniz.Böylece FF ile çarpma N bitiyle sağa kayma olarak gerçekleştirilebilir. Örneğin, FF 1/16 olabilir ve bu nedenle FF ile çarpılır, 4 bitlik sağa kayma. Aksi halde, bu filtre yalnızca bir çıkarma ve bir toplama işlemi gerektirir, ancak sayıların genellikle giriş değerinden daha geniş olması gerekir (aşağıda ayrı bir bölümde daha fazla sayısal hassasiyetle).

Genellikle A / D okumalarını gerekenden çok daha hızlı alırım ve bu filtrelerden ikisini uygularım. Bu serideki iki RC filtresinin dijital eşdeğeridir ve geri alma frekansının üstünde 12 dB / oktav azaltır. Bununla birlikte, A / D okumaları için, zaman cevabındaki filtreye bakmak, zaman cevabını dikkate alarak daha uygundur. Bu, ölçtüğünüz şey değiştiğinde sisteminizin ne kadar hızlı bir değişiklik göreceğini söyler.

Bu filtrelerin tasarlanmasını kolaylaştırmak için (yalnızca FF seçmek ve kaç tanesinin basamaklandırılacağına karar vermek anlamına gelir), programımı FILTBITS kullanıyorum. Basamaklı filtre serisindeki her FF için kaydırma bitlerinin sayısını belirtirsiniz ve adım cevabını ve diğer değerleri hesaplar. Aslında bunu genellikle PLOTFILT sarma betiğimden geçiriyorum. Bu, bir CSV dosyası yapan FILTBITS uygulamasını çalıştırır ve ardından CSV dosyasını çizer. Örneğin, burada "PLOTFILT 4 4" sonucudur:

PLOTFILT parametresindeki iki parametre, yukarıda açıklanan türde basamaklı iki filtre olacağı anlamına gelir. 4 değerleri, FF ile çarpımı gerçekleştirmek için vardiya bitlerinin sayısını gösterir. Bu nedenle iki FF değeri bu durumda 1/16'dır.

Kırmızı iz, birim adım tepkisidir ve bakılması gereken en önemli şeydir. Örneğin, eğer giriş anında değişirse, birleştirilmiş filtrenin çıktısının 60 yinelemede yeni değerin% 90'ına yerleşeceğini söyler. % 95 uzlaşma süresini önemsiyorsanız, yaklaşık 73 yineleme beklemeniz gerekir ve% 50 uzlaştırma süresi için yalnızca 26 yineleme beklemeniz gerekir.

Yeşil iz size tek bir tam genlikli ani çıkışını gösterir. Bu size rastgele gürültü bastırma hakkında bir fikir verir. Tek bir numunenin çıktıda% 2,5'ten fazla bir değişikliğe neden olmayacağı görülüyor.

Mavi iz, bu filtrenin beyaz gürültü ile ne yaptığı hakkında öznel bir his vermek. Bu zorlu bir test değildir, çünkü PLOTFILT'in bu çalışması için beyaz gürültü girişi olarak seçilen rasgele sayıların içeriğinin tam olarak ne olduğuna dair hiçbir garanti yoktur. Sadece size ne kadar ezileceği ve ne kadar pürüzsüz olacağı hakkında kabaca bir his vermek.

PLOTFILT, belki FILTBITS ve özellikle PIC ürün yazılımı gelişimi için pek çok faydalı şey Yazılım İndirme sayfamdaki PIC Development Tools yazılım sürümünde bulunmaktadır .

Sayısal hassasiyet hakkında eklendi

Yorumlardan ve şimdi yeni bir cevap görüyorum ki bu filtreyi uygulamak için gereken bit sayısını tartışmaya ilgi var. FF ile çarpmanın , ikili noktanın altında Log 2 (FF) yeni bitler yaratacağını unutmayın . Küçük sistemlerde, FF genellikle 1/2 N olarak seçilir , bu çarpma gerçekte N bitlerinin sağa kayması ile gerçekleşir.

FILT bu nedenle genellikle sabit nokta tamsayıdır. Bunun, matematiğin hiçbirini işlemcinin bakış açısından değiştirmediğini unutmayın. Örneğin, 10 bit A / D değerlerini ve N = 4 (FF = 1/16) değerlerini filtreliyorsanız, 10 bitlik A / D değerlerinin altında 4 kesir bit gerekir. En işlemcilerden biri, 10 bit A / D okumaları nedeniyle 16 bit tamsayı işlemi yapıyor olacaksınız. Bu durumda, tam olarak aynı 16 bit tamsayı işlemlerini yine de yapabilirsiniz, ancak A / D okumalarını 4 bit kaydırılmış halde bırakın. İşlemci farkı bilmiyor ve gerekmiyor. Matematiğin tamamını 16 bit tam sayılar üzerinde yapmak, onları 12,4 sabit nokta veya gerçek 16 bit tam sayılar (16,0 sabit nokta) olarak kabul ettiğinizde çalışır.

Genel olarak, sayısal gösterime bağlı olarak gürültü eklemek istemiyorsanız, her bir filtre direğine N bit eklemeniz gerekir. Yukarıdaki örnekte, ikisinin ikinci filtresinin bilgi kaybetmemek için 10 + 4 + 4 = 18 bit olması gerekir. Pratikte 8 bitlik bir makinede 24 bitlik değerler kullanacağınız anlamına gelir. Teknik olarak sadece ikisinin ikinci kutbu daha geniş bir değere ihtiyaç duyacaktır, ancak ürün yazılımı sadeliği için, genellikle bir filtrenin tüm kutupları için aynı gösterimi ve dolayısıyla aynı kodu kullanırım.

Genellikle bir filtre direği işlemi gerçekleştirmek için bir alt yordam veya makro yazıyorum, sonra bunu her bir direğe uyguladım. Bir alt rutininin mi yoksa makronun da bu projede döngülerin veya program belleğinin daha önemli olup olmamasına bağlı olduğu. Her iki durumda da, FILT'yi güncelleyen alt rutine / makroya YENİ'yi iletmek için bazı çizik durumları kullanıyorum, ancak aynı zamanda bunları YENİ olan aynı çizik durumuna yüklüyorum. Bir sonrakinin YENİ. Bir alt yordam olduğunda, FILT'den hemen sonra güncellenen FILT'ye bir işaretçi işaretinin gelmesi yararlı olur. Bu şekilde alt yordam, birden çok kez çağrılırsa bellekteki ardışık filtrelerde otomatik olarak çalışır. Her yinelemede işlem yapmak için adresi girdiğiniz için bir makro ile bir işaretçiye ihtiyacınız yoktur.

Kod örnekleri

PIC 18 için yukarıda açıklandığı gibi bir makro örneği:

////////////////////////////////////////////////// //////////////////////////////
//
// Makro FİLTRE süzgeci
//
// NEWVAL'deki bir filtre çubuğunu yeni değerle güncelleyin. NEWVAL, güncellendi
// yeni filtrelenmiş değeri içerir.
//
// FILT, filtre durumu değişkeninin adıdır. 24 bit olduğu varsayılır
// geniş ve yerel bankada.
//
// Filtrenin güncellenmesi için formül:
//
// FİLT <- FİLT + FF (YENİDEN - FİLT)
//
// FF ile çarpma, FILTBITS bitlerinin sağa kayması ile gerçekleştirilir.
//
/ makro filtresi
  /yazmak
         dbankif lbankadr
         movf [arg 1] +0, w; YENİDEN <- YENİDEN - FİLT
         subwf newval + 0
         movf [arg 1] +1, w
         subwfb newval + 1
         movf [arg 1] +2, w
         subwfb newval + 2

  /yazmak
  / loop n filtbit; her bit için NEWVAL sağa kaydırmak için bir kez
         rlcf newval + 2, w; NEWVAL doğru bir bit kaydırma
         rrcf newval + 2
         rrcf newval + 1
         rrcf newval + 0
    / endloop

  /yazmak
         movf newval + 0, w; filtreye kaydırılan değeri ekleyin ve NEWVAL'e kaydedin
         addwf [arg 1] +0, w
         movwf [arg 1] +0
         movwf newval + 0

         movf newval + 1, w
         addwfc [arg 1] +1, w
         movwf [arg 1] +1
         movwf newval + 1

         movf newval + 2, w
         addwfc [arg 1] +2, w
         movwf [arg 1] +2
         movwf newval + 2
  / endmac

Ve burada PIC 24 veya dsPIC 30 veya 33 için benzer bir makro:

////////////////////////////////////////////////// //////////////////////////////
//
// Makro FİLTRE ffbits
//
// Bir düşük geçişli filtrenin durumunu güncelleyin. Yeni giriş değeri W1: W0
// ve güncellenecek filtre durumu W2 ile gösterilir.
//
// Güncellenen filtre değeri W1'de de döndürülecek: W0 ve W2 gösterecek
// filtre durumunu geçen ilk hafızaya. Bu nedenle bu makro olabilir.
// art arda kademeli düşük geçişli filtreler dizisini güncellemek için çağrılır.
//
// Filtre formülü:
//
// FİLT <- FİLT + FF (YENİ - FİLT)
//
// FF ile çarpım, aritmetik sağa kayması ile gerçekleştirilir.
// FFBITS.
//
// UYARI: W3 çöktü.
//
/ makro filtresi
  / var new ffbits integer = [arg 1]; kaydırılacak bit sayısını al

  /yazmak
  / write "; Bir kutup düşük geçişli filtreleme gerçekleştirin, kaydırma bitleri =" ffbits
  /yazmak " ;"

         alt w0, [w2 ++], w0; YENİ - FİLT -> W1: W0
         alt w1, [w2 -], w1

         lsr w0, # [v ffbits], w0; sonucu W1: W0 yönünde kaydır
         sl w1, # [- 16 ffbit], w3
         ior w0, w3, w0
         asr w1, # [v ffbits], w1

         w0 ekle, [w2 ++], w0; W1: W0’da nihai sonucu elde etmek için FILT ekleyin
         addc wl, [w2--], wl

         mov w0, [w2 ++]; sonucu filtre durumuna yaz, ilerletici gösterici
         mov w1, [w2 ++]

  /yazmak
  / endmac

Bu örneklerin her ikisi de, yerleşik makro olanaklarından her ikisinden daha yetenekli olan PIC assembler ön işlemcimi kullanarak makro olarak uygulanır .


1
+1 - haklı olarak para. Ekleyeceğim tek şey, hareketli ortalama filtrelerin bazı görevlerle eşzamanlı olarak gerçekleştirildiklerinde (bir ultrason jeneratörü sürmek için bir sürücü dalga formu üretmek gibi), T'nin hareketli olduğu yerde 1 / T harmoniklerini filtrelemeleridir. ortalama süre.
Jason S

2
Güzel cevap, ama sadece iki şey. Birincisi: yanlış bir filtre seçimine yol açan mutlaka dikkat eksikliği değildir; Benim durumumda, farktan hiç bahsetmedim ve aynısı mezun olmayan insanlar için de geçerli. Yani bazen sadece cehalettir. Ancak ikincisi: neden daha yüksek dereceli bir tane kullanmak yerine iki birinci dereceden dijital filtreyi basamaklandırıyorsunuz? (sadece anlamak için eleştirmiyorum)
clabacchio

3
iki basamaklı tek kutuplu IIR filtre, sayısal sorunlara karşı daha sağlamdır ve tasarımı, tek bir 2. dereceden IIR filtreden daha kolaydır; Tradeoff, 2 kademeli aşamada düşük bir Q (= 1/2?) filtresi elde etmenizdir, ancak çoğu durumda bu büyük bir sorun değildir.
Jason S

1
@clabacchio: Bahsetmem gereken bir başka konu da firmware uygulaması. Tek kutuplu alçak geçirgen filtre alt yordamını bir kez yazabilir, daha sonra birçok kez uygulayabilirsiniz. Aslında, genellikle filtreleme durumuna bellekte bir işaretçi almak için böyle bir alt yordam yazıyorum, daha sonra çok kutuplu filtreleri gerçekleştirmek için kolayca arka arkaya çağrılabilmesi için işaretçiyi ilerletmesini istiyorum.
Olin Lathrop

1
1. cevaplarınız için çok teşekkürler - hepsi. Bu IIR Filtresini kullanmaya karar verdim, ancak bu Filtre Standart LowPass Filtresi olarak kullanılmıyor, çünkü Sayaç Değerlerini ortalamalandırmam ve belirli bir Aralıktaki Değişiklikleri tespit etmek için bunları karşılaştırmam gerekiyor. Bu Değerler van, Donanıma bağlı olarak çok farklı boyutlarda olduğundan, bu Donanıma özgü değişikliklere otomatik olarak tepki verebilmek için ortalama almak istedim.
sensslen

18

İki öğenin gücünün ortalamasına (yani 2,4,8,16,32 vb.) Sınırlandırılmasıyla yaşayabilirseniz, bölme, düşük performanslı bir mikroda adanmış bölme olmadan kolayca ve verimli bir şekilde yapılabilir, çünkü biraz vardiya olarak yapılabilir. Her vardiya hakkı, ikisinin bir gücüdür:

avg = sum >> 2; //divide by 2^2 (4)

veya

avg = sum >> 3; //divide by 2^3 (8)

vb.


bu nasıl yardımcı olur? OP, asıl sorunun eski örnekleri hafızada tutmak olduğunu söylüyor.
Jason S

Bu, OP'nin sorusunu hiçbir şekilde ele almıyor.
Rocketmagnet

12
OP, PIC16'da ve halka arabelleği hafızasında bölerek iki sorunu olduğunu düşündü. Bu cevap, bölünmenin zor olmadığını göstermektedir. Kuşkusuz, bellek sorununu ele almaz, ancak SE sistemi kısmi cevaplara izin verir ve kullanıcılar her cevaptan kendileri için bir şeyler alabilir, hatta diğerlerinin cevaplarını düzenleyebilir ve birleştirebilir. Diğer cevapların bazıları bölünme işlemi gerektirdiğinden, PIC16'da buna nasıl verimli bir şekilde ulaşıldığını göstermediklerinden benzer şekilde eksiktirler.
Martin,

8

Orada ise daha az bellek gereksinimleri ile gerçek bir hareketli ortalama filtresi (aka "yük vagonu filtre") için bir cevap, eğer sen altörneklemedeki umursamıyorum. Buna kademeli bir entegratör-tarak filtresi (CIC) denir . Buradaki fikir, belli bir zaman diliminde farklılık göstereceğiniz bir entegratöre sahip olmanız ve anahtar bellek tasarruf cihazının altörnekleme yaparak entegratörün her değerini saklamanıza gerek kalmamasıdır. Aşağıdaki sözde kod kullanılarak uygulanabilir:

function out = filterInput(in)
{
   const int decimationFactor = /* 2 or 4 or 8 or whatever */;
   const int statesize = /* whatever */
   static int integrator = 0;
   static int downsample_count = 0;
   static int ringbuffer[statesize];
   // don't forget to initialize the ringbuffer somehow
   static int ringbuffer_ptr = 0;
   static int outstate = 0;

   integrator += in;
   if (++downsample_count >= decimationFactor)
   {
     int oldintegrator = ringbuffer[ringbuffer_ptr];
     ringbuffer[ringbuffer_ptr] = integrator;
     ringbuffer_ptr = (ringbuffer_ptr + 1) % statesize;
     outstate = (integrator - oldintegrator) / (statesize * decimationFactor);
   }
   return outstate;
}

Etkili hareketli ortalama uzunluğunuz decimationFactor*statesizeancak statesizenumuneleri tutmanız yeterlidir . Açıkçası, sizin statesizeve decimationFactor2'nin gücündeyseniz daha iyi performans elde edersiniz , böylelikle bölme ve kalan operatörler, vardiya ve maske ile değiştirilir.


Postscript: Olin ile, ortalama bir filtreden önce her zaman basit IIR filtrelerini göz önünde bulundurmanız gerektiğini kabul ediyorum. Bir boxcar filtresinin frekans boşluğuna ihtiyacınız yoksa, 1 veya 2 kutuplu bir düşük geçiş filtresi muhtemelen iyi çalışacaktır.

Öte yandan, çürüme amaçlarına göre filtreliyorsanız (yüksek örnekleme oranlı bir girdi alarak ve düşük oranlı bir işlemle kullanım için ortalaması alıyorsanız), bir CIC filtresi tam olarak aradığınız şey olabilir. (özellikle stateize = 1 kullanabiliyorsanız ve ringbuffer'dan yalnızca önceki bir bütünleştirici değerle tamamen uzak durursanız)


8

Olin Lathrop'un Dijital Sinyal İşleme yığın borsasında önceden tanımladığı (II. Çok güzel resimler içerir.)

y [n] = αx [n] + (1-α) y [n-1]

Bu sadece tamsayılar kullanılarak ve aşağıdaki kodu kullanarak bölme olmadan uygulanabilir (bellekten yazarken biraz hata ayıklamaya ihtiyaç duyabilir).

/**
*  @details    Implement a first order IIR filter to approximate a K sample 
*              moving average.  This function implements the equation:
*
*                  y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
*
*  @param      *filter - a Signed 15.16 fixed-point value.
*  @param      sample - the 16-bit value of the current sample.
*/

#define BITS 2      ///< This is roughly = log2( 1 / alpha )

short IIR_Filter(long *filter, short sample)
{
    long local_sample = sample << 16;

    *filter += (local_sample - *filter) >> BITS;

    return (short)((*filter+0x8000) >> 16);     ///< Round by adding .5 and truncating.
}

Bu filtre, alfa değerini 1 / K olarak ayarlayarak son K numunelerinin hareketli bir ortalamasına yaklaşır. Tarafından önceki kod yapın #defineing BITSK = 16 kümesi için, yani, (K) log2 için BITSK, 4 = 4 grubu BITS2, vs.

(Bir değişiklik alır almaz burada listelenen kodları doğrulayacağım ve gerekirse bu cevabı düzenleyeceğim.)


6

İşte bir tek kutuplu düşük geçişli filtre (hareketli ortalama, kesim frekansı = CutoffFrequency). Çok basit, çok hızlı, harika çalışıyor ve ek yükü neredeyse hiç bellek yok.

Not: Tüm değişkenler, newInput'takiler hariç, filtre işlevinin ötesinde bir kapsam içerir.

// One-time calculations (can be pre-calculated at compile-time and loaded with constants)
DecayFactor = exp(-2.0 * PI * CutoffFrequency / SampleRate);
AmplitudeFactor = (1.0 - DecayFactor);

// Filter Loop Function ----- THIS IS IT -----
double Filter(double newInput)
{
   MovingAverage *= DecayFactor;
   MovingAverage += AmplitudeFactor * newInput;

   return (MovingAverage);
}

Not: Bu tek kademeli bir filtredir. Filtrenin keskinliğini artırmak için birden fazla aşama birlikte basamaklandırılabilir. Birden fazla aşama kullanıyorsanız, telafi etmek için DecayFactor (Cutoff-Frekans ile ilgili olarak) ayarlamanız gerekir.

Ve açıkçası ihtiyacınız olan tek şey, herhangi bir yere yerleştirilen bu iki satırdır, kendi işlevlerine ihtiyaçları yoktur. Bu filtre, hareketli ortalama giriş sinyalini temsil etmeden önce hızlanma süresine sahiptir. Bu hızlanma süresini atlamanız gerekirse, MovingAverage'i 0 yerine newInput değerinin ilk değerine başlatabilirsiniz ve ilk newInput öğesinin bir outlier olmadığını umabilirsiniz.

(CutoffFrequency / SampleRate), 0 ile 0,5 arasında bir aralığa sahiptir. DecayFactor, 0 ile 1 arasında, genellikle 1'e yakın bir değerdir.

Tek duyarlıklı yüzdürmeler çoğu şey için yeterince iyidir, sadece çiftleri tercih ediyorum. Tamsayılara uymanız gerekiyorsa, DecayFactor ve Genlik Faktörünü, tamsayı olarak tam sayı olarak saklanan ve paydayı 2 olan bir tamsayı gücü olan kesirli tamsayılara dönüştürebilirsiniz. filtre döngüsü sırasında bölmek zorunda kalmak yerine payda). Örneğin, DecayFactor = 0.99 ise ve tamsayı kullanmak istiyorsanız, DecayFactor = 0.99 * 65536 = 64881'i ayarlayabilirsiniz. Ardından, istediğiniz zaman filtre döngünüzdeki DecayFactor ile çarpmanız yeterlidir, sadece sonucu kaydırın >> 16.

Bu konuda daha fazla bilgi için, çevrimiçi mükemmel bir kitap, özyinelemeli filtreler ile ilgili 19. bölüm: http://www.dspguide.com/ch19.htm

PS Hareketli Ortalama paradigması için, ihtiyaçlarınızla daha alakalı olabilecek DecayFactor ve AmplitudeFactor ayarlarının yapılmasına farklı bir yaklaşım, diyelim diyelim, yaklaşık 6 öğe birlikte ortalama, isteğe bağlı olarak, 6 öğe ekleyip bölmek 6, böylece AmplitudeFactor'ı 1/6, DecayFactor'ı ise (1.0 - AmplitudeFactor) olarak ayarlayabilirsiniz.


4

Basit bir IIR filtreli bazı uygulamalar için hareketli ortalamaları yaklaşık olarak belirleyebilirsiniz.

ağırlık 0..255 değerdir, yüksek değerler = ortalama için daha kısa zaman çizelgesi

Değer = (yeni değer * ağırlık + değer * (256 ağırlık)) / 256

Yuvarlama hatalarından kaçınmak için, değer normalde uzun olur; bunun için 'gerçek' değeriniz olarak yalnızca daha yüksek sıralı baytlar kullanırsınız.


3

Diğer herkes, IIR'ye karşı FIR'ın yararına ve iki bölümün gücüne dair yorum yaptı. Sadece bazı uygulama detaylarını vermek istiyorum. Aşağıda, FPU'su olmayan küçük mikrodenetleyiciler üzerinde iyi çalışmaktadır. Çarpma yok ve N'yi iki kişilik bir güç olarak tutarsanız, tüm bölümler tek çevrim bit kaydırmadır.

Temel FIR halkası tamponu: son N değerinin çalışma arabelleğini ve arabellekteki tüm değerlerin çalışan SUM'sini sakla. Her yeni örnek geldiğinde, arabellekteki en eski değeri SUM'dan çıkarın, yeni örnekle değiştirin, yeni örneği SUM'a ekleyin ve SUM / N çıktısını alın.

unsigned int Filter(unsigned int sample){
    static unsigned int buffer[N];
    static unsigned char oldest = 0;
    static unsigned long sum;

    sum -= buffer[oldest];
    sum += sample;
    buffer[oldest] = sample;
    oldest += 1;
    if (oldest >= N) oldest = 0;

    return sum/N;
}

Modifiye IIR halka tamponu: son N değerinin çalışmakta olan TOPLAMINI sakla Her yeni bir örnek geldiğinde, SUM - = SUM / N, yeni numuneyi ekleyin ve SUM / N çıktısını alın.

unsigned int Filter(unsigned int sample){
    static unsigned long sum;

    sum -= sum/N;
    sum += sample;

    return sum/N;
}

Seni doğru okuyorsam, birinci dereceden bir IIR filtresi açıklıyorsun; Çıkardığınız değer, düşen en eski değer değil, önceki değerlerin ortalamasıdır. Birinci dereceden IIR filtreleri kesinlikle yararlı olabilir, ancak çıkışın tüm periyodik sinyaller için aynı olduğunu söylerken ne demek istediğinizi tam olarak bilmiyorum. 10 KHz'lik bir örnek hızında, 100Hz kare dalgayı 20 aşamalı bir kutu filtreye beslemek, 20 örnek için eşit bir şekilde yükselen, 30 için yüksek, 20 örnek için eşit şekilde düşen ve 30 için düşük oturan bir sinyal verecektir. IIR filtresi ...
supercat

... keskin bir şekilde yükselmeye başlayan ve kademeli olarak girişin maksimum seviyesine yakın (ancak değil) seviyeye inen, sonra keskin şekilde düşmeye başlayan ve kademeli olarak girişin minimum seviyesine yakın olan bir dalga üretecektir. Çok farklı davranışlar.
supercat

Haklısın, iki tür filtre karıştırıyordum. Bu gerçekten de birinci dereceden bir IIR. Cevabımı eşleşecek şekilde değiştiriyorum. Teşekkürler.
Stephen Collings

Bir sorun, basit bir hareketli ortalamanın yararlı olabileceği ya da olmayabileceğidir. Bir IIR filtresiyle, nispeten az kalker içeren hoş bir filtre elde edebilirsiniz. Tanımladığınız FIR size yalnızca zaman içinde bir dikdörtgen verebilir - sıklıkta bir kıvrım - ve yan lobları yönetemezsiniz. Saat tiklerini kaldırabilirseniz, simetrik olarak ayarlanabilen bir FIR yapmak için birkaç tamsayı çarpımıyla atmak iyi olabilir.
Scott Seidman

@ScottSeidman: Eğer FIR'nin her aşaması basit bir şekilde girdilerin o aşamaya ve önceki saklanan değerine çıktı veriyorsa ve ardından girişi saklarsa (biri sayısal aralıktaysa, toplamı kullanabilir) ortalamadan ziyade). Bunun bir kutu filtresinden daha iyi olup olmadığı uygulamaya bağlıdır (örneğin, toplam 1ms gecikmeli bir kutu filtresinin adım yanıtı, örneğin giriş değiştiğinde kötü bir d2 / dt yükselmesi olur ve tekrar 1ms sonra, toplam 1ms gecikmeli bir filtre için mümkün olan minimum d / dt).
supercat,

2

As mikeselectricstuff gerçekten hafıza ihtiyacını azaltmak gerekir ve (yerine dikdörtgen darbenin) bir üstel olduğunu belirten dürtü yanıtı sakıncası yoksa, söz konusu, ben bir için giderdim ortalama hareketli üstel filtresi. Onları yoğun olarak kullanırım. Bu tür bir filtre ile, herhangi bir tampon gerekmez. N numunelerini saklamak zorunda değilsin. Sadece bir tane. Böylece, bellek gereksinimleriniz N faktörü ile azalır.

Ayrıca, bunun için herhangi bir bölüme ihtiyacınız yok. Sadece çarpımlar. Kayan nokta aritmetiğine erişiminiz varsa, kayan nokta çarpımlarını kullanın. Aksi takdirde, tamsayı çarpımlarını yapın ve sağa kaydırın. Ancak, 2012'deyiz ve kayan noktalı sayılarla çalışmanıza izin veren derleyicileri (ve MCU'ları) kullanmanızı öneririm.

Daha fazla bellek verimli ve daha hızlı olmanın yanı sıra (herhangi bir dairesel tampondaki öğeleri güncellemeniz gerekmez), bunun da daha doğal olduğunu söyleyebilirim , çünkü üstel bir dürtü yanıtı, çoğu durumda doğanın davranış biçimiyle daha iyi eşleşir.


5
Kayan nokta sayıları kullanmanızın önerisiyle aynı fikirde değilim. OP muhtemelen bir sebepten dolayı 8 bitlik bir mikrodenetleyici kullanıyor. Donanım kayan nokta desteğine sahip 8 bitlik bir mikrodenetleyici bulmak zor bir iş olabilir (herhangi bir bilginiz var mı?). Ve donanım desteği olmadan kayan noktalı sayıları kullanmak çok kaynak yoğun bir iş olacaktır.
PetPaulsen

5
Her zaman kayan nokta özelliğine sahip bir işlem kullanmanız gerektiğini söylemek aptalca. Ayrıca, herhangi bir işlemci kayan nokta yapabilir, bu sadece bir hız meselesidir. Gömülü dünyada, inşa maliyetindeki birkaç sent anlamlı olabilir.
Olin Lathrop

@Olin Lathrop ve PetPaulsen: FPU'su olan bir MCU kullanması gerektiğini asla söylemedim. Cevabımı tekrar okudum. "(Ve MCU'lar)" ile, MCU'ların kayan nokta aritmetiği ile akışkan bir şekilde çalışabilecek kadar güçlü olduğu anlamına gelir, bu da tüm MCU'lar için geçerli değildir.
Telaclavo

4
Sadece bir kutuplu düşük geçişli filtre için kayan nokta (donanım VEYA yazılımı) kullanmanıza gerek yoktur.
Jason S

1
Eğer kayan nokta operasyonları olsaydı, ilk etapta bölünmeye itiraz etmezdi.
Federico Russo

0

IIR filtresinin @olin ve @supercat tarafından neredeyse değinildiği, ancak diğerleri tarafından açıkça görülmediği bir sorun, yuvarlama işleminin bazı kesinsizliklere yol açtığı (ve potansiyel olarak önyargı / kısma) olduğu: kullanıldığında, sağa kayma yeni numunenin LSB'lerini sistematik olarak ortadan kaldırır. Bu, dizinin ne kadar süreceği, ortalamanın hiçbir zaman bunları göz önünde bulundurmayacağı anlamına gelir.

Örneğin, yavaş yavaş azalan bir seri (8,8,8, ..., 8,7,7,7, ... 7,6,6) varsayalım ve ortalamanın başlangıçta gerçekten 8 olduğunu varsayalım. İlk "7" numunesi, filtre kuvveti ne olursa olsun, ortalamayı 7'ye getirecektir. Sadece bir örnek için. 6, vb. İçin aynı hikaye. Şimdi tam tersini düşünün: seri yükseliyor. Örnek, değişmesi için yeterince büyük olana kadar ortalama sonsuza kadar 7 kalacaktır.

Tabii ki, "önyargı" için 1/2 ^ N / 2 ekleyerek düzeltebilirsiniz, ancak bu kesin problemi çözmez: bu durumda azalan seri numune 8-1 olana kadar sonsuza dek 8'de kalır. / 2 ^ (N / 2). Örneğin N = 4 için, sıfırın üzerindeki herhangi bir örnek ortalamayı değişmeden tutacaktır.

Bunun bir çözümün kayıp LSB'lerin akümülatörünü elinde tutacağına inanıyorum. Ancak, kodun hazır olmasını yeterince zorlamadım ve diğer bazı seri durumlarda IIR gücüne zarar vermeyeceğinden emin değilim (örneğin, 7,9,7,9'un 8'e ortalama gelip gelmeyeceği) .

@Olin, iki aşamalı cascade'inizin de bir açıklama yapması gerekiyor. İlk ortalamanın ikinci besleyiciye beslenmesiyle iki ortalama değeri mi demek istiyorsun?Her bir yinelemede ? Bunun faydası nedir?

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.