Calcolo della media mobile veloce ed efficiente in termini di memoria


33

Sto cercando una soluzione efficiente in termini di tempo e memoria per calcolare una media mobile in C. Devo evitare di dividere perché sono su un PIC 16 che non ha un'unità di divisione dedicata.

Al momento, memorizzo semplicemente tutti i valori in un buffer ad anello e semplicemente memorizzo e aggiorno la somma ogni volta che arriva un nuovo valore. Questo è davvero efficiente, ma purtroppo utilizza la maggior parte della mia memoria disponibile ...


3
Non credo che ci sia un modo più efficiente di spazio per farlo.
Rocketmagnet,

4
@JobyTaffey bene, è un algoritmo abbastanza diffuso sui sistemi di controllo e richiede di gestire risorse hardware limitate. Quindi penso che troverà più aiuto qui che su SO.
clabacchio

3
@Joby: ci sono alcune rughe su questa domanda che sono rilevanti per i piccoli sistemi con risorse limitate. Vedi la mia risposta Lo faresti in modo molto diverso su un sistema di grandi dimensioni come le persone SO sono abituate. Questo è emerso molto nella mia esperienza di progettazione elettronica.
Olin Lathrop,

1
Sono d'accordo. Questo è abbastanza appropriato per questo forum, in quanto si riferisce ai sistemi integrati.
Rocketmagnet,

Risposte:


55

Come altri hanno già detto, dovresti considerare un filtro IIR (risposta all'impulso infinito) piuttosto che il filtro FIR (risposta agli impulsi finiti) che stai utilizzando ora. C'è di più, ma a prima vista i filtri FIR sono implementati come convoluzioni esplicite e filtri IIR con equazioni.

Il particolare filtro IIR che uso molto nei microcontrollori è un filtro passa basso unipolare. Questo è l'equivalente digitale di un semplice filtro analogico RC. Per la maggior parte delle applicazioni, queste avranno caratteristiche migliori rispetto al filtro a scatola che si sta utilizzando. La maggior parte degli usi di un filtro a scatola che ho riscontrato sono il risultato di qualcuno che non presta attenzione alla classe di elaborazione del segnale digitale, non a causa della necessità delle loro caratteristiche particolari. Se vuoi solo attenuare le alte frequenze che sai essere rumore, un filtro passa basso unipolare è meglio. Il modo migliore per implementarne uno digitalmente in un microcontrollore è di solito:

FILT <- FILT + FF (NUOVO - FILT)

FILT è un pezzo di stato persistente. Questa è l'unica variabile persistente necessaria per calcolare questo filtro. NOVITÀ è il nuovo valore che il filtro viene aggiornato con questa iterazione. FF è la frazione del filtro , che regola la "pesantezza" del filtro. Guarda questo algoritmo e vedi che per FF = 0 il filtro è infinitamente pesante poiché l'output non cambia mai. Per FF = 1, in realtà non esiste alcun filtro poiché l'output segue solo l'input. I valori utili sono nel mezzo. Sui sistemi di piccole dimensioni si sceglie FF come 1/2 Nin modo che la moltiplicazione per FF possa essere realizzata come spostamento a destra per N bit. Ad esempio, FF potrebbe essere 1/16 e il moltiplicarsi per FF quindi uno spostamento a destra di 4 bit. In caso contrario, questo filtro richiede solo una sottrazione e una aggiunta, sebbene i numeri debbano in genere essere più ampi del valore di input (maggiori informazioni sulla precisione numerica in una sezione separata di seguito).

Di solito prendo letture A / D molto più velocemente di quanto siano necessarie e applico due di questi filtri in cascata. Questo è l'equivalente digitale di due filtri RC in serie e si attenua di 12 dB / ottava sopra la frequenza di rolloff. Tuttavia, per le letture A / D è di solito più pertinente esaminare il filtro nel dominio del tempo considerando la sua risposta al gradino. Questo ti dice quanto velocemente il tuo sistema vedrà un cambiamento quando cambia la cosa che stai misurando.

Per facilitare la progettazione di questi filtri (il che significa solo selezionare FF e decidere quanti di questi devono essere messi in cascata), utilizzo il mio programma FILTBITS. Si specifica il numero di bit di spostamento per ciascun FF nella serie di filtri in cascata e calcola la risposta del passo e altri valori. In realtà di solito eseguo questo tramite il mio script wrapper PLOTFILT. Questo esegue FILTBITS, che crea un file CSV, quindi traccia il file CSV. Ad esempio, ecco il risultato di "PLOTFILT 4 4":

I due parametri su PLOTFILT indicano che ci saranno due filtri in cascata del tipo sopra descritto. I valori di 4 indicano il numero di bit di spostamento per realizzare la moltiplicazione per FF. I due valori FF sono quindi 1/16 in questo caso.

La traccia rossa è la risposta del passo unitario ed è la cosa principale da guardare. Ad esempio, questo ti dice che se l'input cambia istantaneamente, l'output del filtro combinato si assesterà al 90% del nuovo valore in 60 iterazioni. Se ti interessa il tempo di assestamento del 95%, devi aspettare circa 73 iterazioni e per il tempo di assestamento del 50% solo 26 iterazioni.

La traccia verde mostra l'output di un singolo picco di ampiezza completa. Questo ti dà un'idea della soppressione del rumore casuale. Sembra che nessun singolo campione provochi una variazione superiore al 2,5% nell'output.

La traccia blu è di dare una sensazione soggettiva di ciò che questo filtro fa con il rumore bianco. Questo non è un test rigoroso poiché non vi è alcuna garanzia quale sia esattamente il contenuto dei numeri casuali raccolti come input di rumore bianco per questa esecuzione di PLOTFILT. È solo per darti un'idea approssimativa di quanto verrà schiacciato e di quanto sia liscio.

PLOTFILT, forse FILTBITS, e molte altre cose utili, in particolare per lo sviluppo del firmware PIC, sono disponibili nella versione del software PIC Development Tools nella pagina dei download del mio software .

Aggiunto sulla precisione numerica

Vedo dai commenti e ora una nuova risposta che è interessante discutere il numero di bit necessari per implementare questo filtro. Notare che la moltiplicazione per FF creerà il Log 2 (FF) nuovi bit sotto il punto binario. Sui sistemi di piccole dimensioni, FF viene solitamente scelto come 1/2 N in modo tale che questa moltiplicazione sia effettivamente realizzata con uno spostamento a destra di N bit.

FILT è quindi generalmente un numero intero a virgola fissa. Si noti che questo non cambia nulla della matematica dal punto di vista del processore. Ad esempio, se si stanno filtrando le letture A / D a 10 bit e N = 4 (FF = 1/16), sono necessari 4 bit di frazione al di sotto delle letture A / D a 10 bit interi. Nella maggior parte dei processori, eseguiresti operazioni con numeri interi a 16 bit a causa delle letture A / D a 10 bit. In questo caso, è ancora possibile eseguire esattamente le stesse operazioni di numero intero a 16 bit, ma iniziare con le letture A / D lasciate spostate di 4 bit. Il processore non conosce la differenza e non è necessario. Fare matematica su interi a 16 bit interi funziona se li consideri come 12,4 punti fissi o veri 16 bit interi (16,0 punti fissi).

In generale, è necessario aggiungere N bit per ogni polo del filtro se non si desidera aggiungere rumore a causa della rappresentazione numerica. Nell'esempio sopra, il secondo filtro di due dovrebbe avere 10 + 4 + 4 = 18 bit per non perdere informazioni. In pratica su una macchina a 8 bit ciò significa che useresti valori a 24 bit. Tecnicamente solo il secondo polo di due avrebbe bisogno di un valore più ampio, ma per semplicità del firmware di solito uso la stessa rappresentazione, e quindi lo stesso codice, per tutti i poli di un filtro.

Di solito scrivo una subroutine o una macro per eseguire un'operazione sui poli del filtro, quindi la applico a ciascun polo. Se una subroutine o macro dipende dal fatto che i cicli o la memoria del programma siano più importanti in quel particolare progetto. In entrambi i casi, utilizzo uno stato di scratch per passare NOVITÀ nella subroutine / macro, che aggiorna FILT, ma carica anche quello nello stesso stato di scratch NOVITÀ. Ciò semplifica l'applicazione di più poli poiché il FILT aggiornato di un polo è il NUOVO del prossimo. Quando una subroutine, è utile avere un puntatore puntare a FILT durante il passaggio, che viene aggiornato subito dopo FILT sulla via di uscita. In questo modo la subroutine opera automaticamente su filtri consecutivi in ​​memoria se chiamata più volte. Con una macro non è necessario un puntatore poiché si passa l'indirizzo per operare su ogni iterazione.

Esempi di codice

Ecco un esempio di una macro come descritto sopra per un PIC 18:

////////////////////////////////////////////////// //////////////////////////////
//
// Macro FILTER filt
//
// Aggiorna un polo filtro con il nuovo valore in NEWVAL. NEWVAL è aggiornato a
// contiene il nuovo valore filtrato.
//
// FILT è il nome della variabile di stato del filtro. Si presume che sia 24 bit
// largo e nella banca locale.
//
// La formula per l'aggiornamento del filtro è:
//
// FILT <- FILT + FF (NEWVAL - FILT)
//
// La moltiplicazione per FF viene eseguita da uno spostamento destro dei bit FILTBITS.
//
/ filtro macro
  /Scrivi
         dbankif lbankadr
         movf [arg 1] +0, w; NEWVAL <- NEWVAL - FILT
         subwf newval + 0
         movf [arg 1] +1, sett
         subwfb newval + 1
         movf [arg 1] +2, w
         subwfb newval + 2

  /Scrivi
  / loop n filtbits; una volta per ogni bit per spostare NEWVAL a destra
         rlcf newval + 2, w; sposta NEWVAL a destra di un bit
         rrcf newval + 2
         rrcf newval + 1
         rrcf newval + 0
    / endloop

  /Scrivi
         movf newval + 0, w; aggiungi valore spostato nel filtro e salva in NEWVAL
         addwf [arg 1] +0, w
         movwf [arg 1] +0
         movwf newval + 0

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

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

Ed ecco una macro simile per un PIC 24 o dsPIC 30 o 33:

////////////////////////////////////////////////// //////////////////////////////
//
// Macro filtri FILTER
//
// Aggiorna lo stato di un filtro passa-basso. Il nuovo valore di input è in W1: W0
// e lo stato del filtro da aggiornare è indicato da W2.
//
// Anche il valore del filtro aggiornato verrà restituito in W1: W0 e W2 punteranno
// nella prima memoria dopo lo stato del filtro. Questa macro può quindi essere
// invocato in successione per aggiornare una serie di filtri passa basso in cascata.
//
// La formula del filtro è:
//
// FILT <- FILT + FF (NEW - FILT)
//
// dove la moltiplicazione per FF viene eseguita da uno spostamento aritmetico destro di
// FFBITS.
//
// AVVISO: W3 è stato eliminato.
//
/ filtro macro
  / var new ffbits integer = [arg 1]; ottieni il numero di bit da spostare

  /Scrivi
  / write "; Esegue il filtro passa basso a un polo, shift bit =" ffbits
  /Scrivi " ;"

         sub w0, [w2 ++], w0; NUOVO - FILT -> W1: W0
         subb w1, [w2--], w1

         lsr w0, # [v ffbits], w0; sposta il risultato in W1: W0 a destra
         sl w1, # [- 16 ffbits], w3
         ior w0, w3, w0
         asr w1, # [v ffbits], w1

         aggiungi w0, [w2 ++], w0; aggiungi FILT per ottenere il risultato finale in W1: W0
         addc w1, [w2--], w1

         mov w0, [w2 ++]; scrivere il risultato nello stato del filtro, avanzare puntatore
         mov w1, [w2 ++]

  /Scrivi
  / endmac

Entrambi questi esempi sono implementati come macro usando il mio preprocessore del mio assemblatore PIC , che è più capace di una delle strutture macro incorporate.


1
+1 - proprio sul denaro. L'unica cosa che aggiungerei è che i filtri della media mobile hanno il loro posto quando vengono eseguiti in modo sincrono per qualche compito (come la produzione di una forma d'onda di azionamento per guidare un generatore di ultrasuoni) in modo da filtrare le armoniche di 1 / T dove T è lo spostamento tempo medio.
Jason S

2
Bella risposta, ma solo due cose. Primo: non è necessariamente la mancanza di attenzione che porta alla scelta di un filtro sbagliato; nel mio caso, non mi è mai stato insegnato la differenza, e lo stesso vale per le persone non laureate. Quindi a volte è solo ignoranza. Ma il secondo: perché fai cascata due filtri digitali del primo ordine invece di usarne uno di ordine superiore? (solo per capire, non sto criticando)
clabacchio

3
due filtri IIR unipolari in cascata sono più robusti per problemi numerici e più facili da progettare rispetto a un singolo filtro IIR di secondo ordine; il compromesso è che con 2 stadi a cascata si ottiene un filtro Q basso (= 1/2?), ma nella maggior parte dei casi non è un grosso problema.
Jason S,

1
@clabacchio: un altro problema che avrei dovuto menzionare è l'implementazione del firmware. È possibile scrivere una subroutine di filtro passa basso unipolare una volta, quindi applicarla più volte. Infatti di solito scrivo una tale subroutine per portare un puntatore in memoria allo stato del filtro, quindi far avanzare il puntatore in modo che possa essere chiamato facilmente in successione per realizzare filtri multipolari.
Olin Lathrop,

1
1. grazie mille per le tue risposte, tutte. Ho deciso di utilizzare questo filtro IIR, ma questo filtro non viene utilizzato come filtro LowPass standard, poiché ho bisogno di calcolare la media dei valori del contatore e confrontarli per rilevare le modifiche in un determinato intervallo. dato che questi valori possono avere dimensioni molto diverse a seconda dell'hardware, ho voluto prendere una media per poter reagire automaticamente a questi cambiamenti specifici dell'hardware.
sensslen,

18

Se riesci a vivere con la limitazione di una potenza di due numeri di elementi in media (cioè 2,4,8,16,32 ecc.), La divisione può essere eseguita facilmente ed efficacemente su un micro a bassa prestazione senza divisione dedicata perché può essere fatto come un po 'di spostamento. Ogni turno a destra ha una potenza di due, ad esempio:

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

o

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

eccetera.


come può essere d'aiuto? L'OP afferma che il problema principale è conservare in memoria campioni passati.
Jason S,

Ciò non affronta affatto la domanda del PO.
Rocketmagnet,

12
L'OP ha pensato di avere due problemi, dividendo in un PIC16 e memoria per il suo buffer di anello. Questa risposta mostra che la divisione non è difficile. Certo, non risolve il problema di memoria, ma il sistema SE consente risposte parziali e gli utenti possono prendere qualcosa da ciascuna risposta per se stessi, o persino modificare e combinare le risposte degli altri. Poiché alcune delle altre risposte richiedono un'operazione di divisione, sono analogamente incomplete poiché non mostrano come ottenere questo in modo efficiente su un PIC16.
Martin,

8

V'è una risposta per un filtro a media mobile vera (alias "filtro carro merci"), con requisiti di memoria meno, se non ti dispiace downsampling. Si chiama filtro integratore a pettine in cascata (CIC). L'idea è che si dispone di un integratore di cui si prendono le differenze in un determinato periodo di tempo e che il dispositivo chiave per il risparmio di memoria è che tramite il downsampling non è necessario memorizzare tutti i valori dell'integratore. Può essere implementato utilizzando il seguente pseudocodice:

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;
}

La lunghezza media mobile effettiva è decimationFactor*statesizema è sufficiente conservare i statesizecampioni. Ovviamente puoi ottenere prestazioni migliori se hai statesizee decimationFactorsei potenze di 2, in modo che gli operatori di divisione e resto vengano sostituiti da turni e maschere.


Postscript: concordo con Olin sul fatto che dovresti sempre considerare semplici filtri IIR prima di un filtro a media mobile. Se non sono necessari i nulli di frequenza di un filtro boxcar, un filtro passa basso a 1 o 2 poli probabilmente funzionerà correttamente.

D'altra parte, se si sta filtrando ai fini della decimazione (prendendo un input ad alta frequenza di campionamento e facendo la media per l'uso con un processo a bassa frequenza), un filtro CIC potrebbe essere proprio quello che stai cercando. (specialmente se puoi usare Statesize = 1 ed evitare del tutto il ringbuffer con un solo valore di integratore precedente)


8

C'è un'analisi approfondita della matematica dietro l'utilizzo del filtro IIR del primo ordine che Olin Lathrop ha già descritto sullo scambio di stack di elaborazione del segnale digitale (include molte belle immagini.) L'equazione per questo filtro IIR è:

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

Questo può essere implementato usando solo numeri interi e nessuna divisione usando il seguente codice (potrebbe essere necessario un po 'di debug mentre stavo digitando dalla memoria.)

/**
*  @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.
}

Questo filtro approssima una media mobile degli ultimi K campioni impostando il valore di alfa su 1 / K. Fallo nel codice precedente #defineing BITSsu LOG2 (K), ovvero per K = 16 impostato BITSsu 4, per K = 4 impostato BITSsu 2, ecc.

(Verificherò il codice elencato qui non appena avrò una modifica e modificherò questa risposta se necessario.)


6

Ecco un filtro passa-basso unipolare (media mobile, con frequenza di taglio = frequenza di taglio). Molto semplice, molto veloce, funziona alla grande e quasi nessun sovraccarico di memoria.

Nota: tutte le variabili hanno un ambito oltre la funzione filtro, ad eccezione di newInput passato

// 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);
}

Nota: questo è un filtro a stadio singolo. È possibile collegare in cascata più fasi per aumentare la nitidezza del filtro. Se usi più di uno stadio, dovrai compensare DecayFactor (in relazione alla frequenza di taglio) per compensare.

E ovviamente tutto ciò di cui hai bisogno sono quelle due linee posizionate ovunque, non hanno bisogno della propria funzione. Questo filtro ha un tempo di accelerazione prima che la media mobile rappresenti quella del segnale di ingresso. Se devi bypassare quel tempo di accelerazione, puoi semplicemente inizializzare MovingAverage sul primo valore di newInput anziché su 0 e sperare che il primo newInput non sia un valore anomalo.

(CutoffFrequency / SampleRate) ha un intervallo compreso tra 0 e 0,5. DecayFactor è un valore compreso tra 0 e 1, generalmente vicino a 1.

I galleggianti a precisione singola sono abbastanza buoni per la maggior parte delle cose, preferisco solo i doppi. Se è necessario attenersi ai numeri interi, è possibile convertire DecayFactor e Amplitude Factor in numeri interi frazionari, in cui il numeratore è memorizzato come numero intero e il denominatore ha una potenza intera di 2 (quindi è possibile spostare a destra il bit come denominatore piuttosto che dover dividere durante il ciclo del filtro). Ad esempio, se DecayFactor = 0,99 e si desidera utilizzare numeri interi, è possibile impostare DecayFactor = 0,99 * 65536 = 64881. Quindi, ogni volta che si moltiplica per DecayFactor nel ciclo del filtro, basta spostare il risultato >> 16.

Per ulteriori informazioni su questo, un eccellente libro online, capitolo 19 sui filtri ricorsivi: http://www.dspguide.com/ch19.htm

PS Per il paradigma della media mobile, un approccio diverso all'impostazione di DecayFactor e AmplitudeFactor che potrebbe essere più rilevante per le tue esigenze, supponiamo che tu voglia il precedente, circa 6 elementi in media insieme, facendolo discretamente, aggiungeresti 6 elementi e dividi per 6, quindi puoi impostare AmplitudeFactor su 1/6 e DecayFactor su (1.0 - AmplitudeFactor).


4

È possibile approssimare una media mobile per alcune applicazioni con un semplice filtro IIR.

il peso è 0..255 valore, valori alti = tempi più brevi per la media

Valore = (nuovo valore * peso + valore * (256 peso)) / 256

Per evitare errori di arrotondamento, il valore sarebbe normalmente un valore lungo, di cui si utilizzano solo byte di ordine superiore come valore "effettivo".


3

Tutti gli altri hanno commentato a fondo l'utilità di IIR contro FIR e sulla divisione del potere di due. Vorrei solo fornire alcuni dettagli di implementazione. Di seguito funziona bene su piccoli microcontrollori senza FPU. Non c'è moltiplicazione e se mantieni N una potenza di due, tutta la divisione è bit-shift a ciclo singolo.

Buffer di anello FIR di base: mantenere un buffer in esecuzione degli ultimi N valori e un SUM in esecuzione di tutti i valori nel buffer. Ogni volta che arriva un nuovo campione, sottrarre il valore più vecchio nel buffer da SUM, sostituirlo con il nuovo campione, aggiungere il nuovo campione a SUM e produrre SUM / 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;
}

Buffer di anello IIR modificato: mantenere una SUM in esecuzione degli ultimi N valori. Ogni volta che arriva un nuovo campione, SUM - = SUM / N, aggiungi il nuovo campione e genera SUM / N.

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

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

    return sum/N;
}

Se ti sto leggendo bene, stai descrivendo un filtro IIR del primo ordine; il valore che stai sottraendo non è il valore più vecchio che sta cadendo, ma è invece la media dei valori precedenti. I filtri IIR del primo ordine possono sicuramente essere utili, ma non sono sicuro di cosa intendi quando suggerisci che l'uscita è la stessa per tutti i segnali periodici. A una frequenza di campionamento di 10 KHz, l'invio di un'onda quadra di 100 Hz in un filtro box da 20 stadi produrrà un segnale che aumenta in modo uniforme per 20 campioni, rimane alto per 30, scende uniformemente per 20 campioni e basso per 30. Un primo ordine Filtro IIR ...
supercat,

... produrrà un'onda che inizia bruscamente a salire e si livella gradualmente vicino (ma non a) il massimo in ingresso, quindi inizia bruscamente a scendere e si livella gradualmente vicino (ma non a) il minimo in ingresso. Comportamento molto diverso.
supercat,

Hai ragione, stavo confondendo due tipi di filtro. Questo è davvero un IIR di primo ordine. Sto cambiando la mia risposta per abbinare. Grazie.
Stephen Collings,

Un problema è che una media mobile semplice può o non può essere utile. Con un filtro IIR, puoi ottenere un bel filtro con relativamente pochi calcoli. Il FIR che descrivi può darti solo un rettangolo in tempo - un sinc in freq - e non puoi gestire i lobi laterali. Può valere la pena di lanciare alcuni moltiplicatori di numeri interi per renderlo un FIR sintonizzabile simmetrico se è possibile risparmiare i tick dell'orologio.
Scott Seidman,

@ScottSeidman: nessuna necessità di moltiplicare se uno ha semplicemente ogni stadio della FIR o genera la media dell'input a quello stadio e il suo valore memorizzato precedente, quindi memorizza l'input (se uno ha l'intervallo numerico, si potrebbe usare la somma piuttosto che nella media). Il fatto che sia meglio di un filtro box dipende dall'applicazione (la risposta al passo di un filtro box con un ritardo totale di 1 ms, ad esempio, avrà un brutto picco d2 / dt quando cambia l'ingresso, e di nuovo 1 ms più tardi, ma avrà il minimo possibile d / dt per un filtro con un ritardo totale di 1 ms).
supercat

2

Come diceva mikeselectricstuff , se hai davvero bisogno di ridurre le tue esigenze di memoria e non ti dispiace che la tua risposta all'impulso sia un esponenziale (anziché un impulso rettangolare), sceglierei un filtro esponenziale a media mobile . Li uso ampiamente. Con quel tipo di filtro, non è necessario alcun buffer. Non è necessario memorizzare N campioni precedenti. Solo uno. Pertanto, i requisiti di memoria vengono ridotti di un fattore N.

Inoltre, non è necessaria alcuna divisione per questo. Solo moltiplicazioni. Se hai accesso all'aritmetica in virgola mobile, usa le moltiplicazioni in virgola mobile. Altrimenti, fai le moltiplicazioni di interi e sposta a destra. Tuttavia, siamo nel 2012 e ti consiglierei di utilizzare compilatori (e MCU) che ti consentano di lavorare con numeri in virgola mobile.

Oltre ad essere più efficiente in termini di memoria e più veloce (non è necessario aggiornare gli elementi in nessun buffer circolare), direi che è anche più naturale , perché una risposta all'impulso esponenziale si adatta meglio al modo in cui la natura si comporta, nella maggior parte dei casi.


5
Non sono d'accordo con la tua raccomandazione di usare numeri in virgola mobile. L'OP utilizza probabilmente un microcontrollore a 8 bit per un motivo. Trovare un microcontrollore a 8 bit con supporto hardware in virgola mobile potrebbe essere un compito difficile (ne conosci qualcuno?). E l'utilizzo di numeri in virgola mobile senza supporto hardware sarà un'attività molto dispendiosa in termini di risorse.
PetPaulsen,

5
Dire che dovresti sempre usare un processo con capacità in virgola mobile è semplicemente stupido. Inoltre, qualsiasi processore può fare in virgola mobile, è solo una questione di velocità. Nel mondo incorporato, alcuni centesimi nel costo di costruzione possono essere significativi.
Olin Lathrop,

@Olin Lathrop e PetPaulsen: non ho mai detto che avrebbe dovuto usare un MCU con FPU hardware. Rileggi la mia risposta. Per "(e MCU)" intendo MCU abbastanza potenti da funzionare con l'aritmetica in virgola mobile del software in modo fluido, il che non è il caso di tutte le MCU.
Telaclavo,

4
Non è necessario utilizzare il virgola mobile (hardware o software) solo per un filtro passa basso a 1 polo.
Jason S

1
Se avesse avuto operazioni in virgola mobile, in primo luogo non si sarebbe opposto alla divisione.
Federico Russo,

0

Un problema con il filtro IIR quasi toccato da @olin e @supercat ma apparentemente ignorato da altri è che l'arrotondamento introduce alcune imprecisioni (e potenzialmente bias / troncamento): supponendo che N sia una potenza di due, e solo l'aritmetica intera è utilizzato, lo spostamento a destra elimina sistematicamente gli LSB del nuovo campione. Ciò significa che per quanto tempo potrà mai essere la serie, la media non ne terrà mai conto.

Ad esempio, supponiamo che una serie stia lentamente diminuendo (8,8,8, ..., 8,7,7,7, ... 7,6,6,) e supponiamo che la media sia effettivamente 8 all'inizio. Il pugno "7" campione porterà la media a 7, qualunque sia la potenza del filtro. Solo per un campione. Stessa storia per 6, ecc. Ora pensa al contrario: la serie sale. La media rimarrà su 7 per sempre, fino a quando il campione non sarà abbastanza grande da farlo cambiare.

Certo, puoi correggere il "bias" aggiungendo 1/2 ^ N / 2, ma questo non risolverà davvero il problema della precisione: in questo caso le serie decrescenti rimarranno per sempre a 8 fino a quando il campione è 8-1 / 2 ^ (N / 2). Ad esempio per N = 4, qualsiasi campione sopra lo zero manterrà invariata la media.

Credo che una soluzione implichi il mantenimento di un accumulatore degli LSB persi. Ma non ho fatto abbastanza per avere il codice pronto, e non sono sicuro che non danneggerebbe la potenza IIR in alcuni altri casi di serie (ad esempio se 7,9,7,9 sarebbe in media 8 allora) .

@Olin, anche la tua cascata a due stadi avrebbe bisogno di qualche spiegazione. Intendi mantenere due valori medi con il risultato del primo immesso nel secondo in ogni iterazione? Qual è il vantaggio di questo?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.