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.