36 votos

Rápida y eficiente cálculo de promedio de movimiento de memoria

Estoy buscando un tiempo y la memoria solución eficiente para calcular una media móvil en el C. I la necesidad de evitar la división porque estoy en un PIC de 16, que no ha dedicado la división de la unidad.

Por el momento, me acaba de guardar todos los valores en un búfer de anillo y simplemente almacenar y actualizar la suma cada vez que un nuevo valor llega. Este es realmente eficiente, pero por desgracia utiliza la mayor parte de mi memoria disponible...

58voto

RelaXNow Puntos 1164

Como otros han mencionado, se debe considerar una IIR (infinite impulse response) filtro en lugar de la FIR (finite impulse response) filtro está utilizando ahora. Hay más, pero a primera vista filtros FIR se implementan como explícito circunvoluciones y los filtros IIR con ecuaciones.

El particular filtro IIR yo uso mucho en los microcontroladores es un polo simple filtro de paso bajo. Este es el equivalente digital de una simple R-C filtro analógico. Para la mayoría de las aplicaciones, estos tienen mejores características que el cuadro de filtro que usted está usando. La mayoría de los usos de un filtro de caja que he encontrado son el resultado de alguien que no preste atención en el procesamiento de la señal digital de clase, no como un resultado de la necesidad de sus características particulares. Si sólo desea atenuar las altas frecuencias que usted sabe que son el ruido, un solo polo filtro de paso bajo es mejor. La mejor manera de implementar digitalmente en un microcontrolador es generalmente:

FILT <-- FILT + FF(NUEVO - FILT)

FILT es una pieza de los compuestos del estado. Esta es la única variable continua que usted necesita para calcular este filtro. NUEVO es el nuevo valor que el filtro se actualiza con esta iteración. FF es el filtro de fracción, que ajusta la "pesadez" del filtro. Mira este algoritmo y ver que para FF = 0 el filtro es infinitamente pesado desde la salida no cambia nunca. Para FF = 1, no hay ningún filtro en todo desde la salida de sólo sigue a la entrada. Los valores útiles están en el medio. En pequeños sistemas de elegir FF a ser de 1/2N , de modo que al multiplicar por FF puede ser realizado como un desplazamiento derecha por N bits. Por ejemplo, FF podría ser de 1/16 y la multiplique por FF, por tanto, un derecho de desplazamiento de 4 bits. De lo contrario, este filtro necesita sólo una resta y un complemento, aunque el número general debe ser mayor que el valor de la entrada (más en la precisión numérica en una sección aparte, más adelante).

Por lo general tengo Un a/D de lecturas significativamente más rápido de lo que sea necesario, y aplicar dos de estos filtros en cascada. Este es el equivalente digital de dos R-C filtros en serie, y atenúa las 12 dB/octava por encima de la atenuación de la frecuencia. Sin embargo, para Un a/D de lecturas es generalmente más relevantes para mirar el filtro en el dominio del tiempo, considerando su respuesta al escalón. Esto indica la velocidad a la que el sistema va a ver un cambio cuando la cosa está la medición de los cambios.

Para facilitar el diseño de estos filtros (que sólo significa escoger FF y la decisión de cómo muchos de ellos a la cascada), puedo utilizar mi programa FILTBITS. Puede especificar el número de desplazamiento de bits para cada uno de los FF en la cascada de la serie de filtros, y se calcula la respuesta al escalón y otros valores. En realidad yo suelo correr a través de mi guión envoltorio PLOTFILT. Este se ejecuta FILTBITS, lo que hace que un archivo CSV, luego traza el archivo CSV. Por ejemplo, aquí está el resultado de "PLOTFILT 4 4":

Los dos parámetros a PLOTFILT significa que habrá dos filtros en cascada del tipo descrito anteriormente. Los valores de 4 indique el número de desplazamiento de bits a darse cuenta de la multiplique por FF. Los dos FF valores son, por tanto, 1/16 en este caso.

El trazo rojo es la unidad de respuesta gradual, y es la principal cosa a tener en cuenta. Por ejemplo, este le dice que si la entrada cambia de forma instantánea, la salida del filtro combinado se asentará en el 90% del nuevo valor en 60 iteraciones. Si usted cuida alrededor del 95% de tiempo de adaptación, a continuación, usted tiene que esperar alrededor de 73 iteraciones, y para el 50% de tiempo de adaptación a sólo 26 iteraciones.

El verde de seguimiento muestra la salida de una sola completo de la amplitud de pico. Esto le da a usted alguna idea de lo aleatorio de supresión de ruido. Parece que no solo muestra hará que más de un 2,5% de cambio en la salida.

El trazo azul es para dar una sensación subjetiva de lo que este filtro hace con el ruido blanco. Esto no es una prueba rigurosa ya que no hay ninguna garantía de lo que es exactamente el contenido de los números aleatorios elegido como el ruido blanco de entrada para esta ejecución de PLOTFILT. Es sólo para darle un áspero sentimiento de cuánto será aplastado y lo suave que es.

PLOTFILT, tal vez FILTBITS, y un montón de otras cosas útiles, especialmente para los PIC de desarrollo de firmware está disponible en la foto de Desarrollo de software de Herramientas de liberación en mi página de descargas de Software.

Agregó acerca de la precisión numérica

Veo en los comentarios, y ahora una nueva respuesta que hay interés en los debates sobre el número de bits necesarios para aplicar este filtro. Tenga en cuenta que al multiplicar por FF va a crear un Registrode 2(FF) nueva de bits por debajo del punto binario. En sistemas pequeños, FF suele ser elegido para ser de 1/2N , de modo que esta se multiplican en realidad es realizado por un desplazamiento derecha de N bits.

FILT es, por tanto, normalmente un punto fijo entero. Tenga en cuenta que esto no cambia ninguna de las matemáticas desde el procesador de punto de vista. Por ejemplo, si usted está filtrado de 10 bits a/D de lecturas y N = 4 (FF = 1/16), entonces usted necesita 4 fracción bits por debajo de los 10 bits entero a/D de lecturas. Una mayoría de los procesadores, usted estaría haciendo operaciones con números enteros de 16 bits, debido a la 10 bits a/D de lecturas. En este caso, usted todavía puede hacer exactamente el mismo 16 bits entero opertions, pero hay que empezar con el a/D lecturas quedaron en el pasado por 4 bits. El procesador no sabe la diferencia y no es necesario. Haciendo las matemáticas sobre el conjunto de enteros de 16 bits funciona si consideramos 12.4 punto fijo o verdadero enteros de 16 bits (16.0 punto fijo).

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

Normalmente escribo una subrutina o una macro para realizar un filtro de polo de la operación, a continuación, aplicar a cada polo. Si una subrutina o macro depende de si los ciclos o de la memoria de programa más importante en ese proyecto en particular. De cualquier manera, yo uso algunos arañazos estado para pasar de NUEVO a la subrutina/macro, que actualiza FILT, sino también las cargas que en el mismo rasguño estado de NUEVA era. Esto hace que sea fácil de aplicar múltiples polos desde la actualización de la FILT de uno de los polos es el NUEVO de la siguiente. Cuando una subrutina, es útil tener un puntero apunte a FILT en la forma en que se actualiza sólo después de FILT en el camino de salida. De esa manera la subrutina funciona automáticamente consecutivos de filtros en la memoria si llama varias veces. Con una macro que usted no necesita un puntero desde que te paso en la dirección para operar en cada iteración.

Ejemplos De Código

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

////////////////////////////////////////////////////////////////////////////////
//
// Macro FILTRO de filt
//
// Actualización de un filtro de polo con el nuevo valor en NEWVAL. NEWVAL se actualiza para
// contiene el nuevo filtrado de valor.
//
// FILT es el nombre del filtro de variable de estado. Se supone que ser de 24 bits
// ancho y en el banco local.
//
// La fórmula para la actualización del filtro es:
//
// FILT <-- FILT + FF(NEWVAL - FILT)
//
// El multiplicar por FF se logra mediante un desplazamiento derecha de FILTBITS bits.
//
/macro filtro
/escritura
 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

/escritura
 /n bucle filtbits ;una vez por cada bit a cambio NEWVAL derecho
 rlcf newval+2, w ;cambio NEWVAL a la derecha un bit
 rrcf newval+2
 rrcf newval+1
 rrcf newval+0
/endloop

/escritura
 movf newval+0, w ;agregar valor desplazado en el filtro y guardar 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í es 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 es en W1 W0
// y el filtro de estado para estar actualizado es señalado por el W2.
//
// La actualización del valor de filtro también le será devuelto en W1 W0 y W2 de punto
// para el primer recuerdo que pasado el filtro de estado. Esta macro puede por lo tanto ser
// se invoca en la sucesión a la actualización de una serie de cascada de filtros paso bajo.
//
// El filtro fórmula es:
//
// FILT <-- FILT + FF(NUEVO - FILT)
//
// donde el multiplicar por FF es realizado por un desplazamiento aritmético a la derecha de
// FFBITS.
//
// ADVERTENCIA: W3 es la papelera.
//
/macro filtro
 /var nueva ffbits integer = [arg 1] ;obtener el número de bits a desplazar

/escritura
 /escritura "; Realizar un polo filtro de paso bajo, el cambio de los bits = " ffbits
 /escritura " ;"

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

 lsr w0, #[v ffbits], w0 ;en cambio el resultado en W1 W0 derecho
 sl w1, #[- 16 ffbits], w3
 ior w0, w3, w0
 asr w1, #[v ffbits], w1

 agregar w0, [w2++], w0 ;agregar FILT para hacer de resultado final en W1 W0
 addc w1, [w2--], w1

 mov w0, [w2++] ;escribir resultado para el filtro de estado, avanzar puntero
 mov w1, [w2++]

/escritura
 /endmac

Estos dos ejemplos son implementados como macros con mi PIC ensamblador de preprocesador, que es más capaz que cualquiera de los macro integrada de las instalaciones.

19voto

Jacob Griffin Puntos 126

Si se puede vivir con la restricción de una potencia de dos número de elementos a la media (es decir 2,4,8,16,32 etc) entonces la brecha fácil y eficiente posible en un micro de bajo rendimiento con ninguna división dedicada porque se puede hacer como un cambio de broca. Cada turno 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.

9voto

bill weaver Puntos 2524

Hay algunos análisis en profundidad de las matemáticas detrás de el uso de la primera orden IIR filtro que Olin Lathrop ya se ha descrito en el Procesamiento de la Señal Digital de intercambio de la pila (incluye un montón de fotos bonitas.) La ecuación para este filtro IIR es:

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

Esto puede ser implementado usando sólo números enteros y no de la división utilizando el siguiente código (puede ser que necesite algo de depuración como yo estaba escribiendo de 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 la media móvil de los últimos K muestras, estableciendo el valor de la alfa de 1/K. Hacerlo en el código anterior #defineing BITS a LOG2(K), es decir, para K = 16 BITS 4, para K = 4 set BITS a 2, etc.

(Voy a comprobar el código que aparece aquí tan pronto como puedo conseguir un cambio y editar esta respuesta si es necesario.)

8voto

fearphage Puntos 250

No es una respuesta para un verdadero filtro de media móvil (también conocido como "vagón de carga del filtro") con menos requerimientos de memoria, si no te importa la disminución de resolución. Se llama una cascada integrador-filtro de peine (CIC). La idea es que usted tiene un integrador que usted toma diferencias de más de un período de tiempo, y la clave de la memoria del dispositivo de ahorro es que por la disminución de resolución, usted no tiene que almacenar cada valor de la empresa integradora. Puede ser implementado 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;
}

A partir de la media móvil de longitud es de decimationFactor*statesize pero usted sólo tiene que tener alrededor de statesize de las muestras. Obviamente, usted puede conseguir el mejor rendimiento si el statesize y decimationFactor son potencias de 2, de modo que la división y el resto de los operadores se sustituye por turnos y máscara-ands.


Posdata: estoy de acuerdo con Olin que siempre se debe considerar simple de los filtros IIR antes de una media móvil de filtro. Si usted no necesita la frecuencia de los valores null de un vagón de carga del filtro, 1 polo, o de 2 polos del filtro de paso bajo es probable que el trabajo bien.

Por otro lado, si usted está filtrado para los fines de la aniquilación (teniendo una alta tasa de muestreo de entrada y un promedio para su uso por una baja velocidad de proceso), a continuación, un filtro CIC puede ser justo lo que estás buscando. (especialmente si usted puede utilizar statesize=1 y evitar el ringbuffer por completo con un solo anterior integrador de valor)

6voto

Rishabh Mallik Puntos 69

He aquí un solo polo filtro de paso bajo (media móvil, con frecuencia de corte = CutoffFrequency). Muy simple, muy rápido, funciona muy bien, y casi no hay sobrecarga de memoria.

Nota: Todas las variables tienen un alcance más allá de la función de filtro, excepto en el pasado 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: Esta es una sola etapa de filtro. Etapas múltiples se pueden conectar en cascada para aumentar la nitidez del filtro. Si usted usa más de una etapa, usted tendrá que ajustar DecayFactor (como se refiere a la Frecuencia de Corte) para compensar.

Y, obviamente, todo lo que necesita es las dos líneas de colocar en cualquier lugar, que no necesitan su propia función. Este filtro tiene un tiempo de rampa antes de la media móvil representa la de la señal de entrada. Si usted necesita para eludir que el tiempo de rampa, sólo se puede inicializar MovingAverage para el primer valor de newInput en lugar de 0, y la esperanza de que el primer newInput no es un valor atípico.

(CutoffFrequency/frecuencia de muestreo) tiene un rango de entre 0 y 0.5. DecayFactor es un valor entre 0 y 1, en general cerca de 1.

Precisión simple flotadores son lo suficientemente bueno para la mayoría de las cosas, yo prefiero dobles. Si usted necesita pegarse con números enteros, se puede convertir DecayFactor y la Amplitud de los factores en fracciones de números enteros, en la que el numerador se almacena como un entero, y el denominador es una potencia entera de 2, de manera que puede bits se desplazan a la derecha como el denominador en lugar de tener que dividir durante el filtro de bucle). Por ejemplo, si DecayFactor = 0.99, y desea utilizar los números enteros, se puede establecer DecayFactor = 0.99 * 65536 = 64881. Y, a continuación, en cualquier momento que multiplicar por DecayFactor en el filtro de bucle, solo cambian el resultado >> 16.

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

P. S. Por la Media móvil de paradigma, un enfoque diferente de la configuración de DecayFactor y AmplitudeFactor que pueden ser más relevantes para sus necesidades, digamos que usted desea que el anterior, acerca de 6 elementos promediar, hacerlo de forma discreta, te gustaría añadir 6 elementos y dividir por 6, por lo que puede establecer la AmplitudeFactor a 1/6, y DecayFactor a (1.0 - AmplitudeFactor).

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X