Como já mencionado, você deve considerar um filtro IIR (resposta ao impulso infinito) em vez do filtro FIR (resposta ao impulso finito) que você está usando agora. Há mais do que isso, mas à primeira vista os filtros FIR são implementados como convoluções explícitas e filtros IIR com equações.
O filtro IIR específico que eu uso muito em microcontroladores é um filtro passa-baixo monopolar. Este é o equivalente digital de um simples filtro analógico RC. Para a maioria dos aplicativos, eles terão melhores características do que o filtro de caixa que você está usando. A maioria dos usos de um filtro de caixa que encontrei são resultado de alguém que não presta atenção na classe de processamento de sinal digital, e não como resultado de precisar de suas características particulares. Se você apenas deseja atenuar as altas frequências que você sabe que são ruídos, um filtro passa-baixo monopolar é melhor. A melhor maneira de implementar digitalmente um microcontrolador é geralmente:
FILT <- FILT + FF (NOVO - FILT)
FILT é um pedaço de estado persistente. Essa é a única variável persistente que você precisa para calcular esse filtro. NEW é o novo valor que o filtro está sendo atualizado com essa iteração. FF é a fração do filtro , que ajusta o "peso" do filtro. Veja este algoritmo e veja que para FF = 0 o filtro é infinitamente pesado, pois a saída nunca muda. Para FF = 1, não há realmente nenhum filtro, pois a saída segue apenas a entrada. Valores úteis estão no meio. Em sistemas pequenos, você escolhe FF para 1/2 Npara que a multiplicação por FF possa ser realizada como um deslocamento à direita por N bits. Por exemplo, FF pode ser 1/16 e a multiplicação por FF, portanto, um deslocamento à direita de 4 bits. Caso contrário, esse filtro precisará apenas de uma subtração e uma adição, embora os números geralmente precisem ser maiores que o valor de entrada (mais sobre precisão numérica em uma seção separada abaixo).
Normalmente, tomo leituras A / D significativamente mais rápidas do que são necessárias e aplico dois desses filtros em cascata. É o equivalente digital de dois filtros RC em série e atenua 12 dB / oitava acima da frequência de rolloff. No entanto, para leituras A / D, geralmente é mais relevante olhar para o filtro no domínio do tempo, considerando sua resposta ao passo. Isso indica a rapidez com que o sistema verá uma mudança quando a coisa que você está medindo mudar.
Para facilitar o design desses filtros (o que significa apenas escolher FF e decidir quantos deles cascatear), eu uso meu programa FILTBITS. Você especifica o número de bits de deslocamento para cada FF na série em cascata de filtros e calcula a resposta da etapa e outros valores. Na verdade, eu costumo executar isso através do meu script wrapper PLOTFILT. Isso executa FILTBITS, que cria um arquivo CSV e, em seguida, plota o arquivo CSV. Por exemplo, aqui está o resultado de "PLOTFILT 4 4":
Os dois parâmetros para PLOTFILT significam que haverá dois filtros em cascata do tipo descrito acima. Os valores de 4 indicam o número de bits de deslocamento para realizar a multiplicação por FF. Os dois valores de FF são, portanto, 1/16 neste caso.
O traço vermelho é a resposta da etapa da unidade e é a principal coisa a se observar. Por exemplo, isso informa que, se a entrada for alterada instantaneamente, a saída do filtro combinado se ajustará a 90% do novo valor em 60 iterações. Se você se importa com cerca de 95% do tempo de estabilização, precisa aguardar 73 iterações e, por 50%, apenas 26 iterações.
O traço verde mostra a saída de um único pico de amplitude total. Isso lhe dá uma idéia da supressão aleatória de ruído. Parece que nenhuma amostra isolada causará mais de 2,5% de alteração na saída.
O traço azul é dar uma sensação subjetiva do que esse filtro faz com o ruído branco. Este não é um teste rigoroso, pois não há garantia sobre o conteúdo exato dos números aleatórios escolhidos como entrada de ruído branco para esta execução do PLOTFILT. É apenas para lhe dar uma idéia aproximada de quanto será esmagado e quão suave é.
PLOTFILT, talvez FILTBITS e muitas outras coisas úteis, especialmente para o desenvolvimento de firmware PIC, estão disponíveis na versão do software PIC Development Tools na minha página de downloads de software .
Adicionado sobre precisão numérica
Vejo pelos comentários e agora uma nova resposta que há interesse em discutir o número de bits necessários para implementar esse filtro. Observe que a multiplicação por FF criará novos bits do Log 2 (FF) abaixo do ponto binário. Em sistemas pequenos, o FF é geralmente escolhido como 1/2 N, para que essa multiplicação seja efetivamente realizada por um deslocamento correto de N bits.
FILT é, portanto, geralmente um número inteiro de ponto fixo. Observe que isso não altera nada da matemática do ponto de vista do processador. Por exemplo, se você estiver filtrando leituras A / D de 10 bits e N = 4 (FF = 1/16), precisará de 4 bits de fração abaixo das leituras A / D inteiras de 10 bits. Na maioria dos processadores, você faria operações inteiras de 16 bits devido às leituras A / D de 10 bits. Nesse caso, você ainda pode fazer exatamente as mesmas operações de número inteiro de 16 bits, mas comece com as leituras A / D deixadas deslocadas em 4 bits. O processador não sabe a diferença e não precisa. Fazer a matemática em inteiros inteiros de 16 bits funciona se você os considera 12,4 pontos fixos ou inteiros verdadeiros de 16 bits (16,0 pontos fixos).
Em geral, você precisa adicionar N bits a cada pólo de filtro se não desejar adicionar ruído devido à representação numérica. No exemplo acima, o segundo filtro de dois teria que ter 10 + 4 + 4 = 18 bits para não perder informações. Na prática, em uma máquina de 8 bits, isso significa que você usaria valores de 24 bits. Tecnicamente, apenas o segundo pólo de dois precisaria do valor mais amplo, mas, para simplificar o firmware, geralmente uso a mesma representação e, portanto, o mesmo código, para todos os pólos de um filtro.
Normalmente, escrevo uma sub-rotina ou macro para executar uma operação de pólo de filtro e, em seguida, aplico-o a cada pólo. Se uma sub-rotina ou macro depende se os ciclos ou a memória do programa são mais importantes nesse projeto em particular. De qualquer forma, eu uso algum estado zero para passar NEW para a sub-rotina / macro, que atualiza o FILT, mas também carrega esse mesmo estado no mesmo estado em que NEW estava. Isso facilita a aplicação de vários pólos, pois o FILT atualizado de um pólo é o NOVO do próximo. Quando uma sub-rotina, é útil ter um ponteiro apontar para FILT na entrada, que é atualizado para logo após a saída da FILT. Dessa forma, a sub-rotina opera automaticamente em filtros consecutivos na memória, se chamados várias vezes. Com uma macro, você não precisa de um ponteiro, pois passa o endereço para operar em cada iteração.
Exemplos de código
Aqui está um exemplo de uma macro conforme descrito acima para um PIC 18:
//////////////////////////////////////////////////// //////////////////////////////
//
// Macro FILTER filt
//
// Atualize um pólo de filtro com o novo valor em NEWVAL. NEWVAL é atualizado para
// contém o novo valor filtrado.
//
// FILT é o nome da variável de estado do filtro. Supõe-se que sejam 24 bits
// amplo e no banco local.
//
// A fórmula para atualizar o filtro é:
//
// FILT <- FILT + FF (NEWVAL - FILT)
//
// A multiplicação por FF é realizada com o deslocamento à direita dos bits FILTBITS.
//
/ filtro macro
/escrever
dbankif lbankadr
movf [arg 1] +0, w; NEWVAL <- NEWVAL - FILT
subwf newval + 0
movf [argumento 1] +1, w
subwfb newval + 1
movf [argumento 1] +2, w
subwfb newval + 2
/escrever
/ loop n filtbits; uma vez para cada bit deslocar NEWVAL para a direita
rlcf newval + 2, w; desloque NEWVAL para a direita um bit
rrcf newval + 2
rrcf newval + 1
rrcf newval + 0
/ endloop
/escrever
movf newval + 0, w; adicione valor alterado no filtro e salve em NEWVAL
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
E aqui está uma macro semelhante para um PIC 24 ou dsPIC 30 ou 33:
//////////////////////////////////////////////////// //////////////////////////////
//
// FILTRO DE Macro ffbits
//
// Atualiza o estado de um filtro passa-baixo. O novo valor de entrada está em W1: W0
// e o estado do filtro a ser atualizado é apontado por W2.
//
// O valor atualizado do filtro também será retornado em W1: W0 e W2 apontarão
// para a primeira memória após o estado do filtro. Essa macro pode, portanto, ser
// chamado sucessivamente para atualizar uma série de filtros passa-baixas em cascata.
//
// A fórmula do filtro é:
//
// FILT <- FILT + FF (NOVO - FILT)
//
// onde a multiplicação por FF é realizada por um deslocamento aritmético para a direita de
// FFBITS.
//
// AVISO: W3 está na lixeira.
//
/ filtro macro
/ var novo ffbits inteiro = [arg 1]; obtém o número de bits para mudar
/escrever
/ write "; Execute a filtragem passa-baixo de um pólo, shift bits =" ffbits
/escrever " ;"
sub w0, [w2 ++], w0; NEW - FILT -> W1: W0
subb w1, [w2--], w1
lsr w0, # [v ffbits], w0; desloque o resultado em W1: W0 para a direita
sl w1, # [- 16 ffbits], w3
ou w0, w3, w0
asr w1, # [v ffbits], w1
adicione w0, [w2 ++], w0; adicione FILT para obter o resultado final em W1: W0
addc w1, [w2--], w1
mov w0, [w2 ++]; escreve o resultado no estado do filtro, avança o ponteiro
mov w1, [w2 ++]
/escrever
/ endmac
Ambos os exemplos são implementados como macros usando meu pré-processador PIC assembler , que é mais capaz do que qualquer um dos recursos internos de macro.