Cálculo del promedio móvil rápido y eficiente en la memoria

33

Estoy buscando una solución eficiente en tiempo y memoria para calcular un promedio móvil en C. Necesito evitar dividir porque estoy en un PIC 16 que no tiene una unidad de división dedicada.

Por el momento, solo almaceno todos los valores en un buffer de anillo y simplemente almaceno y actualizo la suma cada vez que llega un nuevo valor. Esto es realmente eficiente, pero desafortunadamente usa la mayor parte de mi memoria disponible ...

sensslen
fuente
3
No creo que haya una forma más eficiente de espacio para hacer esto.
Rocketmagnet
44
@JobyTaffey, es un algoritmo bastante utilizado en los sistemas de control y requiere tratar con recursos limitados de hardware. Así que creo que encontrará más ayuda aquí que en SO.
clabacchio
3
@Joby: Hay algunas arrugas sobre esta pregunta que son relevantes para los sistemas pequeños con recursos limitados. Mira mi respuesta. Lo harías de manera muy diferente en un sistema grande como la gente SO está acostumbrada. Esto ha surgido mucho en mi experiencia de diseño de electrónica.
Olin Lathrop
1
Estoy de acuerdo. Esto es bastante apropiado para este foro, ya que se relaciona con los sistemas integrados.
Rocketmagnet
Retraigo

Respuestas:

55

Como otros han mencionado, debe considerar un filtro IIR (respuesta de impulso infinito) en lugar del filtro FIR (respuesta de impulso finito) que está utilizando ahora. Hay más, pero a primera vista los filtros FIR se implementan como convoluciones explícitas y filtros IIR con ecuaciones.

El filtro IIR particular que uso mucho en microcontroladores es un filtro de paso bajo de un polo. Este es el equivalente digital de un filtro analógico RC simple. Para la mayoría de las aplicaciones, estas tendrán mejores características que el filtro de caja que está utilizando. La mayoría de los usos de un filtro de caja que he encontrado son el resultado de alguien que no presta atención en la clase de procesamiento de señal digital, no como resultado de la necesidad de sus características particulares. Si solo desea atenuar las frecuencias altas que sabe que son ruido, un filtro de paso bajo de un polo es mejor. La mejor manera de implementar uno digitalmente en un microcontrolador es generalmente:

FILT <- FILT + FF (NUEVO - FILT)

FILT es una pieza de estado persistente. Esta es la única variable persistente que necesita para calcular este filtro. NUEVO es el nuevo valor que el filtro se está actualizando con esta iteración. FF es la fracción del filtro , que ajusta la "pesadez" del filtro. Mire este algoritmo y vea que para FF = 0 el filtro es infinitamente pesado ya que la salida nunca cambia. Para FF = 1, realmente no hay ningún filtro, ya que la salida solo sigue a la entrada. Los valores útiles están en el medio. En sistemas pequeños, elige FF para que sea 1/2 Npara que la multiplicación por FF se pueda lograr como un desplazamiento a la derecha por N bits. Por ejemplo, FF podría ser 1/16 y la multiplicación por FF, por lo tanto, un desplazamiento a la derecha de 4 bits. De lo contrario, este filtro solo necesita una resta y una suma, aunque los números generalmente deben ser más amplios que el valor de entrada (más sobre precisión numérica en una sección separada a continuación).

Por lo general, tomo lecturas A / D significativamente más rápido de lo necesario y aplico dos de estos filtros en cascada. Este es el equivalente digital de dos filtros RC en serie, y se atenúa 12 dB / octava por encima de la frecuencia de caída. Sin embargo, para las lecturas A / D, generalmente es más relevante mirar el filtro en el dominio del tiempo considerando su respuesta escalonada. Esto le indica qué tan rápido su sistema verá un cambio cuando cambie lo que está midiendo.

Para facilitar el diseño de estos filtros (que solo significa elegir FF y decidir cuántos de ellos en cascada), utilizo mi programa FILTBITS. Usted especifica el número de bits de desplazamiento para cada FF en la serie de filtros en cascada, y calcula la respuesta del paso y otros valores. En realidad, generalmente ejecuto esto a través de mi script de contenedor PLOTFILT. Esto ejecuta FILTBITS, que crea un archivo CSV, luego traza el archivo CSV. Por ejemplo, aquí está el resultado de "PLOTFILT 4 4":

Los dos parámetros para PLOTFILT significan que habrá dos filtros en cascada del tipo descrito anteriormente. Los valores de 4 indican el número de bits de desplazamiento para realizar la multiplicación por FF. Los dos valores FF son, por lo tanto, 1/16 en este caso.

El trazo rojo es la respuesta del paso unitario, y es lo principal a tener en cuenta. Por ejemplo, esto le dice que si la entrada cambia instantáneamente, la salida del filtro combinado se asentará en el 90% del nuevo valor en 60 iteraciones. Si le interesa el 95% del tiempo de establecimiento, debe esperar alrededor de 73 iteraciones, y el 50% del tiempo de establecimiento solo 26 iteraciones.

El trazo verde muestra la salida de un único pico de amplitud completa. Esto le da una idea de la supresión de ruido aleatorio. Parece que ninguna muestra individual causará más de un cambio del 2.5% en la salida.

La traza azul es para dar una sensación subjetiva de lo que este filtro hace con el ruido blanco. Esta no es una prueba rigurosa ya que no hay garantía de cuál era exactamente el contenido de los números aleatorios elegidos como entrada de ruido blanco para esta ejecución de PLOTFILT. Es solo para darle una sensación aproximada de cuánto se aplastará y qué tan suave es.

PLOTFILT, quizás FILTBITS, y muchas otras cosas útiles, especialmente para el desarrollo de firmware PIC, están disponibles en la versión del software PIC Development Tools en mi página de descargas de software .

Agregado sobre precisión numérica

Veo en los comentarios y ahora una nueva respuesta que hay interés en discutir la cantidad de bits necesarios para implementar este filtro. Tenga en cuenta que la multiplicación por FF creará nuevos bits de Log 2 (FF) debajo del punto binario. En sistemas pequeños, FF generalmente se elige como 1/2 N para que esta multiplicación se realice realmente mediante un desplazamiento a la derecha de N bits.

FILT es, por lo tanto, un número entero de punto fijo. Tenga en cuenta que esto no cambia ninguna de las matemáticas desde el punto de vista del procesador. Por ejemplo, si está filtrando lecturas A / D de 10 bits y N = 4 (FF = 1/16), entonces necesita 4 bits de fracción por debajo de las lecturas A / D de enteros de 10 bits. Uno de los procesadores más, estaría haciendo operaciones de enteros de 16 bits debido a las lecturas A / D de 10 bits. En este caso, aún puede hacer exactamente las mismas operaciones de enteros de 16 bits, pero comience con las lecturas A / D desplazadas 4 bits. El procesador no sabe la diferencia y no necesita saberlo. Hacer los cálculos en enteros enteros de 16 bits funciona tanto si los considera 12.4 puntos fijos como si son enteros de 16 bits (16.0 puntos fijos).

En general, debe agregar N bits a cada polo del filtro si no desea agregar ruido debido a la representación numérica. En el ejemplo anterior, el segundo filtro de dos tendría que tener 10 + 4 + 4 = 18 bits para no perder información. En la práctica en una máquina de 8 bits, eso significa que usaría valores de 24 bits. Técnicamente, solo el segundo polo de dos necesitaría el valor más amplio, pero por simplicidad de firmware, generalmente uso la misma representación y, por lo tanto, el mismo código, para todos los polos de un filtro.

Por lo general, escribo una subrutina o macro para realizar una operación de polo de filtro, luego lo aplico a cada polo. Si una subrutina o macro depende de si los ciclos o la memoria del programa son más importantes en ese proyecto en particular. De cualquier manera, utilizo algún estado scratch para pasar NEW a la subrutina / macro, que actualiza FILT, pero también carga eso en el mismo estado scratch en el que estaba NEW. Esto facilita la aplicación de múltiples polos ya que el FILT actualizado de un polo es lo NUEVO de la próxima. Cuando se trata de una subrutina, es útil tener un puntero apuntando a FILT al entrar, que se actualiza justo después de FILT al salir. De esa manera, la subrutina opera automáticamente en filtros consecutivos en la memoria si se llama varias veces. Con una macro, no necesita un puntero ya que pasa la dirección para operar en cada iteración.

Ejemplos de código

Aquí hay un ejemplo de una macro como se describió anteriormente para un PIC 18:

//////////////////////////////////////////////////////////////////////////////// //////////////////////////////
//
// Macro FILTRO filt
//
// Actualice un polo de filtro con el nuevo valor en NEWVAL. NEWVAL se actualiza a
// contiene el nuevo valor filtrado.
//
// FILT es el nombre de la variable de estado del filtro. Se supone que tiene 24 bits.
// ancho y en el banco local.
//
// La fórmula para actualizar el filtro es:
//
// FILT <- FILT + FF (NEWVAL - FILT)
//
// La multiplicación por FF se logra mediante un desplazamiento a la derecha de bits FILTBITS.
//
/ filtro macro
  /escribir
         dbankif lbankadr
         movf [arg 1] +0, w; NEWVAL <- NEWVAL - FILT
         subwf newval + 0
         movf [arg 1] +1, w
         subwfb newval + 1
         movf [arg 1] +2, w
         subwfb newval + 2

  /escribir
  / loop n filtbits; una vez por cada bit para desplazar NEWVAL a la derecha
         rlcf newval + 2, w; desplazar NEWVAL a la derecha un bit
         rrcf newval + 2
         rrcf newval + 1
         rrcf newval + 0
    / endloop

  /escribir
         movf newval + 0, w; agregue un valor desplazado en el filtro y guárdelo en 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

Y aquí hay una macro similar para un PIC 24 o dsPIC 30 o 33:

//////////////////////////////////////////////////////////////////////////////// //////////////////////////////
//
// Macro FILTRO ffbits
//
// Actualiza el estado de un filtro de paso bajo. El nuevo valor de entrada está en W1: W0
// y W2 señala el estado del filtro a actualizar.
//
// El valor del filtro actualizado también se devolverá en W1: W0 y W2 apuntarán
// a la primera memoria más allá del estado del filtro. Esta macro por lo tanto puede ser
// se invoca sucesivamente para actualizar una serie de filtros de paso bajo en cascada.
//
// La fórmula del filtro es:
//
// FILT <- FILT + FF (NUEVO - FILT)
//
// donde la multiplicación por FF se realiza mediante un desplazamiento aritmético a la derecha de
// FFBITS.
//
// ADVERTENCIA: W3 está en la papelera.
//
/ filtro macro
  / var new ffbits integer = [arg 1]; obtener el número de bits para cambiar

  /escribir
  / escribir "; Realizar un filtro de paso bajo de un polo, bits de desplazamiento =" ffbits
  /escribir " ;"

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

         lsr w0, # [v ffbits], w0; desplaza el resultado en W1: W0 a la derecha
         sl w1, # [- 16 ffbits], w3
         ior w0, w3, w0
         Asr w1, # [v ffbits], w1

         agregue w0, [w2 ++], w0; agregue FILT para hacer el resultado final en W1: W0
         addc w1, [w2--], w1

         mov w0, [w2 ++]; escribir el resultado en el estado del filtro, puntero de avance
         mov w1, [w2 ++]

  /escribir
  / endmac

Ambos ejemplos se implementan como macros utilizando mi preprocesador de ensamblador PIC , que es más capaz que cualquiera de las funciones de macro incorporadas.

Olin Lathrop
fuente
1
+1 - justo en el dinero. Lo único que agregaría es que los filtros de promedio móvil tienen su lugar cuando se realizan sincrónicamente en alguna tarea (como producir una forma de onda de impulsión para conducir un generador de ultrasonido) para que filtren armónicos de 1 / T donde T es el movimiento tiempo promedio.
Jason S
2
Buena respuesta, pero solo dos cosas. Primero: no es necesariamente la falta de atención lo que lleva a elegir un filtro incorrecto; en mi caso, nunca me han enseñado la diferencia, y lo mismo se aplica a las personas no graduadas. Entonces, a veces es solo ignorancia. Pero el segundo: ¿por qué coloca en cascada dos filtros digitales de primer orden en lugar de utilizar uno de orden superior? (solo para entender, no estoy criticando)
clabacchio
3
dos filtros IIR monopolares en cascada son más robustos a los problemas numéricos y más fáciles de diseñar que un solo filtro IIR de segundo orden; La desventaja es que con 2 etapas en cascada obtienes un filtro Q bajo (= 1/2?), pero en la mayoría de los casos eso no es un gran problema.
Jason S
1
@clabacchio: Otro problema que debería haber mencionado es la implementación del firmware. Puede escribir una subrutina de filtro de paso bajo de un polo una vez y luego aplicarla varias veces. De hecho, generalmente escribo una subrutina de este tipo para llevar un puntero en memoria al estado del filtro, y luego hacer que avance el puntero para que se pueda llamar en sucesión fácilmente para realizar filtros multipolares.
Olin Lathrop
1
1. Muchas gracias por sus respuestas, todas ellas. Decidí usar este filtro IIR, pero este filtro no se usa como un filtro LowPass estándar, ya que necesito promediar valores de contador y compararlos para detectar cambios en un cierto rango. Dado que estos valores pueden ser de dimensiones muy diferentes según el hardware, quería tomar un promedio para poder reaccionar a estos cambios específicos de hardware automáticamente.
sensslen
18

Si puede vivir con la restricción de una potencia de dos elementos para promediar (es decir, 2,4,8,16,32, etc.), entonces la división se puede hacer fácil y eficientemente en un micro de bajo rendimiento sin división dedicada porque Se puede hacer como un poco de cambio. Cada cambio a la derecha es una potencia de dos, por ejemplo:

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

o

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

etc.

Martín
fuente
¿Cómo ayuda eso? El OP dice que el problema principal es mantener las muestras pasadas en la memoria.
Jason S
Esto no aborda la pregunta del OP en absoluto.
Rocketmagnet
12
El OP pensó que tenía dos problemas, dividiendo en un PIC16 y memoria para su buffer de anillo. Esta respuesta muestra que la división no es difícil. Es cierto que no aborda el problema de memoria, pero el sistema SE permite respuestas parciales, y los usuarios pueden tomar algo de cada respuesta por sí mismos, o incluso editar y combinar las respuestas de otros. Dado que algunas de las otras respuestas requieren una operación de división, están igualmente incompletas ya que no muestran cómo lograr esto de manera eficiente en un PIC16.
Martin
8

No es una respuesta para un filtro de promedio real en movimiento (también conocido como "filtro de vagón de carga") con menos requisitos de memoria, si no le importa la disminución de resolución. Se llama un filtro de peine integrador en cascada (CIC). La idea es que tenga un integrador del que tome diferencias a lo largo de un período de tiempo, y el dispositivo clave de ahorro de memoria es que al reducir el muestreo, no tiene que almacenar todos los valores del integrador. Se puede implementar utilizando el siguiente 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;
}

Su longitud promedio de movimiento efectiva es decimationFactor*statesizepero solo necesita mantener alrededor de las statesizemuestras. Obviamente, puede obtener un mejor rendimiento si su statesizey decimationFactorson potencias de 2, de modo que los operadores de división y resto sean reemplazados por turnos y máscaras.


Posdata: estoy de acuerdo con Olin en que siempre debe considerar filtros IIR simples antes de un filtro de media móvil. Si no necesita los nulos de frecuencia de un filtro de vagón, un filtro de paso bajo de 1 o 2 polos probablemente funcionará bien.

Por otro lado, si está filtrando con el propósito de diezmar (tomar una entrada de alta frecuencia de muestreo y promediarla para usarla en un proceso de baja velocidad), entonces un filtro CIC puede ser justo lo que está buscando. (especialmente si puede usar statesize = 1 y evitar el ringbuffer con un solo valor de integrador anterior)

Jason S
fuente
8

Hay un análisis en profundidad de las matemáticas detrás del uso del filtro IIR de primer orden que Olin Lathrop ya ha descrito en el intercambio de la pila de Procesamiento de señal digital (incluye muchas imágenes bonitas). La ecuación para este filtro IIR es:

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

Esto se puede implementar usando solo números enteros y sin división usando el siguiente código (podría necesitar alguna depuración ya que estaba escribiendo desde la 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.
}

Este filtro se aproxima a un promedio móvil de las últimas K muestras al establecer el valor de alfa en 1 / K. Para ello, en el código anterior por #defineing BITSa LOG2 (K), es decir, para K = 16 conjunto BITSa 4, para K = 4 conjunto BITSa 2, etc.

(Verificaré el código que aparece aquí tan pronto como reciba un cambio y editaré esta respuesta si es necesario).

oosterwal
fuente
6

Aquí hay un filtro de paso bajo unipolar (promedio móvil, con frecuencia de corte = Frecuencia de corte). Muy simple, muy rápido, funciona muy bien y casi no tiene sobrecarga de memoria.

Nota: Todas las variables tienen un alcance más allá de la función de filtro, excepto las pasadas en 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 es un filtro de etapa única. Se pueden conectar en cascada múltiples etapas para aumentar la nitidez del filtro. Si usa más de una etapa, tendrá que ajustar DecayFactor (en relación con la frecuencia de corte) para compensar.

Y obviamente, todo lo que necesita es esas dos líneas ubicadas en cualquier lugar, no necesitan su propia función. Este filtro tiene un tiempo de aceleración antes de que la media móvil represente la de la señal de entrada. Si necesita omitir ese tiempo de aceleración, puede simplemente inicializar MovingAverage al primer valor de newInput en lugar de 0, y esperar que el primer newInput no sea un valor atípico.

(CutoffFrequency / SampleRate) tiene un rango de entre 0 y 0.5. DecayFactor es un valor entre 0 y 1, generalmente cercano a 1.

Los flotadores de precisión simple son lo suficientemente buenos para la mayoría de las cosas, solo prefiero los dobles. Si necesita seguir con los enteros, puede convertir el factor de decaimiento y el factor de amplitud en enteros fraccionarios, en los que el numerador se almacena como el entero, y el denominador es una potencia entera de 2 (por lo que puede desplazarse hacia la derecha como el denominador en lugar de tener que dividir durante el ciclo de filtro). Por ejemplo, si DecayFactor = 0.99, y desea usar números enteros, puede establecer DecayFactor = 0.99 * 65536 = 64881. Y luego, cada vez que multiplique por DecayFactor en su ciclo de filtro, simplemente cambie el resultado >> 16.

Para obtener más información sobre esto, un excelente libro que está en línea, capítulo 19 sobre filtros recursivos: http://www.dspguide.com/ch19.htm

PD Para el paradigma de la media móvil, un enfoque diferente para configurar DecayFactor y AmplitudeFactor que puede ser más relevante para sus necesidades, digamos que quiere el promedio anterior, aproximadamente 6 elementos juntos, haciéndolo discretamente, agregaría 6 elementos y dividiría por 6, por lo que puede configurar AmplitudeFactor en 1/6 y DecayFactor en (1.0 - AmplitudeFactor).

Patricio
fuente
4

Puede aproximar una media móvil para algunas aplicaciones con un filtro IIR simple.

el peso es 0..255 valor, valores altos = escala de tiempo más corta para avaraging

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

Para evitar errores de redondeo, el valor normalmente sería largo, de los cuales solo usará bytes de orden superior como su valor 'real'.

mikeselectricstuff
fuente
3

Todos los demás han comentado a fondo sobre la utilidad de IIR vs. FIR, y sobre la división del poder de dos. Solo me gustaría dar algunos detalles de implementación. Lo siguiente funciona bien en microcontroladores pequeños sin FPU. No hay multiplicación, y si mantienes N una potencia de dos, toda la división es un desplazamiento de bits de un solo ciclo.

Búfer de anillo FIR básico: mantenga un búfer en ejecución de los últimos N valores y una SUMA en ejecución de todos los valores en el búfer. Cada vez que ingrese una nueva muestra, reste el valor más antiguo en el búfer de SUM, reemplácelo con la nueva muestra, agregue la nueva muestra a SUM y envíe 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;
}

Tampón de anillo IIR modificado: mantenga una SUMA en ejecución de los últimos N valores. Cada vez que entra una nueva muestra, SUM - = SUM / N, agrega la nueva muestra y genera SUM / N.

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

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

    return sum/N;
}
Stephen Collings
fuente
Si te estoy leyendo bien, estás describiendo un filtro IIR de primer orden; el valor que está restando no es el valor más antiguo que se está cayendo, sino que es el promedio de los valores anteriores. Los filtros IIR de primer orden ciertamente pueden ser útiles, pero no estoy seguro de lo que quiere decir cuando sugiere que la salida es la misma para todas las señales periódicas. A una frecuencia de muestreo de 10 KHz, la alimentación de una onda cuadrada de 100 Hz en un filtro de caja de 20 etapas producirá una señal que se eleva de manera uniforme para 20 muestras, se eleva a 30, disminuye uniformemente para 20 muestras y baja a 30. Un primer orden Filtro IIR ...
supercat
... producirá una onda que comienza a subir bruscamente y se nivela gradualmente cerca (pero no al) máximo de entrada, luego comienza a caer bruscamente y se nivela gradualmente cerca (pero no al) mínimo de entrada. Comportamiento muy diferente.
supercat
Tienes razón, estaba confundiendo dos tipos de filtro. Este es de hecho un IIR de primer orden. Estoy cambiando mi respuesta para que coincida. Gracias.
Stephen Collings
Un problema es que un promedio móvil simple puede o no ser útil. Con un filtro IIR, puede obtener un buen filtro con relativamente pocos calcs. El FIR que describe solo puede darle un rectángulo a tiempo, un sinc en frecuencia, y no puede manejar los lóbulos laterales. Puede valer la pena agregar algunos múltiplos enteros para que sea un buen FIR simétrico sintonizable si puede ahorrar el tictac del reloj.
Scott Seidman
@ScottSeidman: no es necesario multiplicar si uno simplemente tiene cada etapa de la FIR, ya sea generar el promedio de la entrada a esa etapa y su valor almacenado anterior, y luego almacenar la entrada (si uno tiene el rango numérico, uno podría usar la suma en lugar de la media). Si eso es mejor que un filtro de caja depende de la aplicación (la respuesta escalonada de un filtro de caja con un retraso total de 1 ms, por ejemplo, tendrá un repunte desagradable de d2 / dt cuando cambie la entrada, y nuevamente 1 ms más tarde, pero tendrá el mínimo d / dt posible para un filtro con un retraso total de 1 ms).
supercat
2

Como dijo mikeselectricstuff , si realmente necesita reducir sus necesidades de memoria y no le importa que su respuesta al impulso sea exponencial (en lugar de un pulso rectangular), optaría por un filtro de media móvil exponencial . Los uso ampliamente. Con ese tipo de filtro, no necesita ningún búfer. No tiene que almacenar N muestras pasadas. Solo uno. Entonces, sus requisitos de memoria se reducen por un factor de N.

Además, no necesitas ninguna división para eso. Solo multiplicaciones. Si tiene acceso a la aritmética de coma flotante, use multiplicaciones de coma flotante. De lo contrario, haga multiplicaciones y desplazamientos enteros a la derecha. Sin embargo, estamos en 2012, y le recomendaría que use compiladores (y MCU) que le permitan trabajar con números de punto flotante.

Además de ser más eficiente en memoria y más rápido (no tiene que actualizar elementos en ningún búfer circular), diría que también es más natural , porque una respuesta de impulso exponencial coincide mejor con el comportamiento de la naturaleza, en la mayoría de los casos.

Telaclavo
fuente
55
No estoy de acuerdo con su recomendación de usar números de coma flotante. El OP probablemente usa un microcontrolador de 8 bits por una razón. Encontrar un microcontrolador de 8 bits con soporte de punto flotante de hardware podría ser una tarea difícil (¿conoces alguna?). Y usar números de punto flotante sin soporte de hardware será una tarea muy intensiva en recursos.
PetPaulsen
55
Decir que siempre debe usar un proceso con capacidad de coma flotante es una tontería. Además, cualquier procesador puede hacer coma flotante, es solo una cuestión de velocidad. En el mundo integrado, unos pocos centavos en el costo de construcción pueden ser significativos.
Olin Lathrop
@Olin Lathrop y PetPaulsen: Nunca dije que debería usar un MCU con FPU de hardware. Vuelve a leer mi respuesta. Por "(y MCU)" me refiero a las MCU lo suficientemente potentes como para trabajar con aritmética de coma flotante de software de forma fluida, lo que no es el caso para todas las MCU.
Telaclavo
44
No es necesario usar punto flotante (hardware O software) solo para un filtro de paso bajo de 1 polo.
Jason S
1
Si tuviera operaciones de coma flotante, no se opondría a la división en primer lugar.
Federico Russo
0

Un problema con el filtro IIR como casi tocado por @olin y @supercat pero aparentemente ignorado por otros es que el redondeo introduce cierta imprecisión (y potencialmente sesgo / truncamiento): suponiendo que N es una potencia de dos, y solo la aritmética de enteros es utilizado, el desplazamiento a la derecha elimina sistemáticamente los LSB de la nueva muestra. Eso significa que cuánto tiempo podría durar la serie, el promedio nunca los tendrá en cuenta.

Por ejemplo, suponga una serie que disminuye lentamente (8,8,8, ..., 8,7,7,7, ... 7,6,6,) y suponga que el promedio es de hecho 8 al principio. La primera muestra "7" elevará el promedio a 7, sea cual sea la fuerza del filtro. Solo por una muestra. La misma historia para 6, etc. Ahora piense en lo contrario: la serie sube. El promedio se mantendrá en 7 para siempre, hasta que la muestra sea lo suficientemente grande como para hacer que cambie.

Por supuesto, puede corregir el "sesgo" agregando 1/2 ^ N / 2, pero eso realmente no resolverá el problema de precisión: en ese caso, la serie decreciente permanecerá para siempre en 8 hasta que la muestra sea 8-1 / 2 ^ (N / 2). Para N = 4, por ejemplo, cualquier muestra por encima de cero mantendrá el promedio sin cambios.

Creo que una solución para eso implicaría tener un acumulador de los LSB perdidos. Pero no llegué lo suficientemente lejos como para tener el código listo, y no estoy seguro de que no dañaría el poder de IIR en otros casos de series (por ejemplo, si 7,9,7,9 promediaría a 8 en ese momento) .

@Olin, tu cascada de dos etapas también necesitaría alguna explicación. ¿Te refieres a mantener dos valores promedio con el resultado del primero alimentado al segundo en cada iteración? ¿Cuál es el beneficio de esto?

Chris
fuente