Cálculo da média móvel rápida e eficiente em memória

33

Estou procurando uma solução eficiente em tempo e memória para calcular uma média móvel em C. Preciso evitar a divisão porque estou em um PIC 16 que não possui uma unidade de divisão dedicada.

No momento, apenas armazeno todos os valores em um buffer de anel e simplesmente armazeno e atualizo a soma cada vez que um novo valor chega. Isso é realmente eficiente, mas infelizmente usa a maior parte da minha memória disponível ...

sensslen
fonte
3
Não acho que exista mais maneira eficiente de fazer espaço.
Rocketmagnet
4
@JobyTaffey bem, é um algoritmo bastante usado em sistemas de controle e requer lidar com recursos limitados de hardware. Então, acho que ele encontrará mais ajuda aqui do que no SO.
clabacchio
3
@Joby: Existem algumas rugas sobre essa questão que são relevantes para pequenos sistemas com recursos limitados. Veja minha resposta. Você faria isso de maneira muito diferente em um sistema grande como o SO está acostumado. Isso surgiu muito na minha experiência em projetar eletrônicos.
Olin Lathrop
1
Concordo. Isso é bastante apropriado para este fórum, pois se refere a sistemas embarcados.
Rocketmagnet
Retiro

Respostas:

55

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.

Olin Lathrop
fonte
1
+1 - diretamente no dinheiro. A única coisa que eu acrescentaria é que os filtros médios móveis têm seu lugar quando executados de forma síncrona com alguma tarefa (como produzir uma forma de onda de acionamento para acionar um gerador de ultrassom), para que eles filtrem harmônicos de 1 / T, onde T é o movimento tempo médio.
Jason S
2
Boa resposta, mas apenas duas coisas. Primeiro: não é necessariamente a falta de atenção que leva à escolha de um filtro errado; no meu caso, nunca fui ensinado sobre a diferença, e o mesmo se aplica a pessoas não formadas. Então, às vezes é apenas ignorância. Mas a segunda: por que você coloca em cascata dois filtros digitais de primeira ordem, em vez de usar um filtro de ordem superior? (só para entender, eu não estou criticando)
clabacchio
3
dois filtros IIR unipolares em cascata são mais robustos para problemas numéricos e mais fáceis de projetar do que um único filtro IIR de 2ª ordem; a desvantagem é que, com dois estágios em cascata, você obtém um filtro Q baixo (= 1/2?), mas na maioria dos casos isso não é um grande problema.
Jason S
1
@clabacchio: Outra questão que eu deveria ter mencionado é a implementação do firmware. Você pode escrever uma sub-rotina de filtro passa-baixo monopolar uma vez e aplicá-la várias vezes. De fato, eu normalmente escrevo uma sub-rotina para levar um ponteiro na memória para o estado do filtro e, em seguida, fazer avançar o ponteiro para que possa ser chamado em sucessão facilmente para realizar filtros multipolares.
Olin Lathrop
1
1. muito obrigado por suas respostas - todas elas. Decidi usar este filtro IIR, mas esse filtro não é usado como um filtro LowPass padrão, pois preciso calcular a média dos valores de contador e compará-los para detectar alterações em um determinado intervalo. como esses van de valores têm dimensões muito diferentes, dependendo do hardware, eu queria fazer uma média para poder reagir a essas alterações específicas de hardware automaticamente.
sensslen
18

Se você pode viver com a restrição de uma potência de dois números de itens em média (ou seja, 2,4,8,16,32 etc), a divisão pode ser feita de maneira fácil e eficiente em um micro de baixo desempenho sem divisão dedicada, porque pode ser feito como uma pequena mudança. Cada turno à direita é uma potência de dois, por exemplo:

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

ou

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

etc.

Martin
fonte
como isso ajuda? O OP diz que o principal problema é manter as amostras passadas na memória.
Jason S
Isso não aborda a questão do OP.
Rocketmagnet
12
O OP achou que ele tinha dois problemas, dividindo em um PIC16 e memória para seu buffer de anel. Esta resposta mostra que a divisão não é difícil. É certo que não trata do problema de memória, mas o sistema SE permite respostas parciais, e os usuários podem pegar algo de cada resposta por si mesmos, ou até editar e combinar as respostas de outros. Como algumas das outras respostas exigem uma operação de divisão, elas são igualmente incompletas, pois não mostram como conseguir isso com eficiência em um PIC16.
Martin
8

Não é uma resposta para um filtro de média verdadeiro movimento (aka "filtro de vagão") com requisitos de memória menos, se você não se importa downsampling. É chamado de filtro em cascata de integrador-pente (CIC). A idéia é que você tenha um integrador do qual considere diferenças ao longo de um período de tempo, e o principal dispositivo de economia de memória é que, ao reduzir a amostragem, você não precisa armazenar todos os valores do integrador. Pode ser implementado usando o seguinte pseudocódigo:

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

Seu comprimento médio móvel efetivo é, decimationFactor*statesizemas você só precisa manter as statesizeamostras em volta . Obviamente, você pode obter um melhor desempenho se você statesizee decimationFactorsão potências de 2, para que os operadores de divisão e restante sejam substituídos por turnos e mascaramentos.


Postscript: Eu concordo com Olin que você deve sempre considerar filtros IIR simples antes de um filtro de média móvel. Se você não precisar dos nulos de frequência de um filtro de vagão, um filtro passa-baixo de 1 ou 2 pólos provavelmente funcionará bem.

Por outro lado, se você estiver filtrando para fins de dizimação (obtendo uma entrada com alta taxa de amostra e calculando a média para uso em um processo de baixa taxa), um filtro CIC pode ser exatamente o que você está procurando. (especialmente se você pode usar statesize = 1 e evitar o ringbuffer completamente com apenas um único valor anterior do integrador)

Jason S
fonte
8

Há uma análise aprofundada da matemática por trás do uso do filtro IIR de primeira ordem que Olin Lathrop já descreveu na troca de pilhas do Digital Signal Processing (inclui muitas fotos bonitas). A equação desse filtro IIR é:

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

Isso pode ser implementado usando apenas números inteiros e nenhuma divisão usando o código a seguir (pode precisar de alguma depuração enquanto eu estava digitando na memória).

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

Esse filtro aproxima uma média móvel das últimas K amostras, configurando o valor de alfa como 1 / K. Fazer isso no código anterior por #defineing BITSpara LOG2 (K), ou seja, para K = 16 conjunto BITSde 4, para K = 4 conjunto BITSde 2, etc.

(Verificarei o código listado aqui assim que receber uma alteração e editarei esta resposta, se necessário.)

oosterwal
fonte
6

Aqui está um filtro passa-baixo unipolar (média móvel, com frequência de corte = CutoffFrequency). Muito simples, muito rápido, funciona muito bem e quase não há sobrecarga de memória.

Nota: Todas as variáveis ​​têm escopo além da função de filtro, exceto as passadas em newInput

// 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: Este é um filtro de estágio único. Vários estágios podem ser conectados em cascata para aumentar a nitidez do filtro. Se você usar mais de um estágio, terá que ajustar o DecayFactor (relacionado à Frequência de corte) para compensar.

E, obviamente, tudo o que você precisa são aquelas duas linhas colocadas em qualquer lugar, elas não precisam de suas próprias funções. Este filtro tem um tempo de aceleração antes que a média móvel represente a do sinal de entrada. Se você precisar ignorar esse tempo de aceleração, basta inicializar MovingAverage para o primeiro valor de newInput em vez de 0 e esperar que o primeiro newInput não seja um erro externo.

(CutoffFrequency / SampleRate) tem um intervalo entre 0 e 0,5. DecayFactor é um valor entre 0 e 1, geralmente próximo a 1.

Flutuadores de precisão única são bons o suficiente para a maioria das coisas, eu apenas prefiro duplos. Se você precisar ficar com números inteiros, poderá converter DecayFactor e Amplitude Factor em números fracionais, nos quais o numerador é armazenado como o número inteiro e o denominador é uma potência inteira de 2 (para que você possa mudar de bit para a direita como o número inteiro). denominador em vez de ter que dividir durante o loop do filtro). Por exemplo, se DecayFactor = 0,99 e você deseja usar números inteiros, pode definir DecayFactor = 0,99 * 65536 = 64881. E sempre que você multiplicar por DecayFactor no loop de filtro, basta mudar o resultado >> 16.

Para obter mais informações, um excelente livro on-line, capítulo 19, sobre filtros recursivos: http://www.dspguide.com/ch19.htm

PS Para o paradigma Média Móvel, uma abordagem diferente para definir DecayFactor e AmplitudeFactor que possam ser mais relevantes para suas necessidades, digamos que você queira o anterior, com cerca de 6 itens em média, fazendo isso discretamente, adicionando 6 itens e dividido por 6, para que você possa definir o AmplitudeFactor como 1/6 e DecayFactor como (1.0 - AmplitudeFactor).

Patrick
fonte
4

Você pode aproximar uma média móvel para algumas aplicações com um simples filtro IIR.

peso é 0..255, valores altos = escala de tempo mais curta para a média

Valor = (novo valor * peso + valor * (peso 256)) / 256

Para evitar erros de arredondamento, o valor normalmente seria longo, dos quais você usa apenas bytes de ordem mais alta como seu valor 'real'.

mikeselectricstuff
fonte
3

Todos os demais comentaram minuciosamente a utilidade de IIR vs. FIR e a divisão de poder de dois. Gostaria apenas de dar alguns detalhes de implementação. O abaixo funciona bem em pequenos microcontroladores sem FPU. Não há multiplicação e, se você mantiver N uma potência de dois, toda a divisão é de troca de bits de ciclo único.

Buffer de anel FIR básico: mantenha um buffer em execução dos últimos N valores e um SUM em execução de todos os valores no buffer. Sempre que uma nova amostra chegar, subtraia o valor mais antigo do buffer de SUM, substitua-o pela nova amostra, adicione a nova amostra a SUM e faça a saída 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 de anel IIR modificado: mantenha um SUM em execução dos últimos N valores. Cada vez que uma nova amostra entra, SUM - = SUM / N, adicione a nova amostra e produza SUM / N.

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

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

    return sum/N;
}
Stephen Collings
fonte
Se estou lendo você direito, você está descrevendo um filtro IIR de primeira ordem; o valor que você está subtraindo não é o valor mais antigo que está caindo, mas sim a média dos valores anteriores. Os filtros IIR de primeira ordem certamente podem ser úteis, mas não sei o que você quer dizer quando sugere que a saída é a mesma para todos os sinais periódicos. A uma taxa de amostragem de 10KHz, alimentar uma onda quadrada de 100Hz em um filtro de caixa de 20 estágios produzirá um sinal que aumenta uniformemente para 20 amostras, fica alto por 30, cai uniformemente por 20 amostras e fica baixo por 30. Uma primeira ordem Filtro IIR ...
supercat
... produzirá uma onda que começa bruscamente a subir e se estabiliza gradualmente próximo (mas não no) do máximo de entrada, e então começa bruscamente a cair e gradualmente se estabiliza próximo (mas não no) do mínimo de entrada. Comportamento muito diferente.
Supercat 28/08
Você está certo, eu estava confundindo dois tipos de filtro. Este é realmente um IIR de primeira ordem. Estou mudando minha resposta para combinar. Obrigado.
Stephen Collings
Uma questão é que uma média móvel simples pode ou não ser útil. Com um filtro IIR, você pode obter um bom filtro com relativamente poucos cálculos. O FIR que você descreve pode fornecer apenas um retângulo no tempo - um sinc em freq - e você não pode gerenciar os lóbulos laterais. Pode valer a pena jogar algumas multiplicações de números inteiros para torná-lo um bom FIR simétrico ajustável, se você puder poupar os tiques do relógio.
Scott Seidman
@ScottSeidman: Não há necessidade de multiplicar se alguém simplesmente tem cada estágio do FIR ou produz a média da entrada para esse estágio e seu valor armazenado anterior e depois armazena a entrada (se houver um intervalo numérico, pode-se usar a soma em vez da média). Se isso é melhor do que um filtro de caixa depende da aplicação (a resposta de etapa de um filtro de caixa com um atraso total de 1ms, por exemplo, terá um pico desagradável de d2 / dt quando a entrada for alterada e novamente 1ms depois, mas terá o d / dt mínimo possível para um filtro com um atraso total de 1 ms).
Supercat
2

Como o mikeselectricstuff disse, se você realmente precisa reduzir suas necessidades de memória e não se importa que sua resposta ao impulso seja exponencial (em vez de um pulso retangular), eu usaria uma média móvel exponencial filtro de . Eu os uso extensivamente. Com esse tipo de filtro, você não precisa de nenhum buffer. Você não precisa armazenar N amostras anteriores. Apenas um. Portanto, seus requisitos de memória são reduzidos por um fator de N.

Além disso, você não precisa de nenhuma divisão para isso. Apenas multiplicações. Se você tiver acesso à aritmética de ponto flutuante, use multiplicações de ponto flutuante. Caso contrário, faça multiplicações inteiras e mude para a direita. No entanto, estamos em 2012 e eu recomendo que você use compiladores (e MCUs) que permitem trabalhar com números de ponto flutuante.

Além de ser mais eficiente em termos de memória e mais rápido (você não precisa atualizar itens em nenhum buffer circular), eu diria que também é mais natural , porque uma resposta de impulso exponencial corresponde melhor à maneira como a natureza se comporta, na maioria dos casos.

Telaclavo
fonte
5
Não concordo com a recomendação do uso de números de ponto flutuante. O OP provavelmente usa um microcontrolador de 8 bits por um motivo. Encontrar um microcontrolador de 8 bits com suporte a ponto flutuante por hardware pode ser uma tarefa difícil (você conhece algum?). E usar números de ponto flutuante sem suporte de hardware será uma tarefa que exige muitos recursos.
precisa saber é o seguinte
5
Dizer que você sempre deve usar um processo com capacidade de ponto flutuante é apenas bobagem. Além disso, qualquer processador pode fazer ponto flutuante, é apenas uma questão de velocidade. No mundo incorporado, alguns centavos no custo de construção podem ser significativos.
amigos estão dizendo
@Olin Lathrop e PetPaulsen: Eu nunca disse que ele deveria usar um MCU com FPU de hardware. Releia minha resposta. Por "(e MCUs)", quero dizer MCUs poderosos o suficiente para trabalhar com aritmética de ponto flutuante de software de maneira fluida, o que não é o caso de todas as MCUs.
Telaclavo 20/04/12
4
Não é necessário usar ponto flutuante (hardware OU software) apenas para um filtro passa-baixo de 1 pólo.
Jason S
1
Se ele tivesse operações de ponto flutuante, não se oporia à divisão em primeiro lugar.
Federico Russo
0

Um problema com o filtro IIR quase tocado por @olin e @supercat, mas aparentemente desconsiderado por outros, é que o arredondamento introduz alguma imprecisão (e potencialmente viés / truncamento): assumindo que N é uma potência de dois e que apenas a aritmética inteira é usado, a mudança à direita elimina sistematicamente os LSBs da nova amostra. Isso significa que quanto tempo a série poderia ter, a média nunca levará isso em consideração.

Por exemplo, suponha uma série que diminui lentamente (8,8,8, ..., 8,7,7,7, ... 7,6,6) e assuma que a média é de fato 8 no início. A primeira amostra "7" elevará a média para 7, independentemente da força do filtro. Apenas para uma amostra. Mesma história para 6, etc. Agora, pense no contrário: a série sobe. A média permanecerá em 7 para sempre, até que a amostra seja grande o suficiente para fazê-la mudar.

Obviamente, você pode corrigir o "viés" adicionando 1/2 ^ N / 2, mas isso realmente não resolverá o problema de precisão: nesse caso, a série decrescente permanecerá para sempre em 8 até a amostra ser 8-1 / 2 ^ (N / 2). Para N = 4, por exemplo, qualquer amostra acima de zero manterá a média inalterada.

Acredito que uma solução para isso implicaria manter um acumulador de LSBs perdidos. Mas não cheguei longe o suficiente para ter o código pronto e não tenho certeza de que isso não prejudicaria o poder do IIR em alguns outros casos de séries (por exemplo, se 7,9,7,9 seriam em média 8 até então). .

@Olin, sua cascata de dois estágios também precisaria de algumas explicações. Você quer dizer manter dois valores médios com o resultado do primeiro alimentado no segundo em cada iteração? Qual o benefício disso?

Chris
fonte