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.