Um filtro passa-baixa (LPF) funciona como uma peneira digital, permitindo que sinais de baixa frequência (variações graduais, como a orientação de um dispositivo) passem, enquanto atenua sinais de alta frequência (ruídos abruptos, como interferências eletrônicas).
Em sistemas de sensoriamento, os dados brutos geralmente contêm sinal útil (movimento físico real de mudança lenta) e ruído (flutuações aleatórias de alta frequência). O objteivo principal de um LPF é suavizar as irregularidades, preservando a tendência geral.
Filtro Passa-Baixa de Primeira Ordem (PT1)
A forma mais simples, conhecida como média exponencial ponderada, é definida pela equação de diferenças:
y[n] = α·x[n] + (1-α)·y[n-1]
Onde x[n] é a amostra atual, y[n-1] é a saída anterior e α (coeficiente de suavização) varia entre 0 e 1.
Vantagens: Algoritmo extremamente leve, com baixo custo computacional, exigindo apenas o armazenamento da saída anterior.
Desvantagens: Atenuação limitada (-20dB/dec após a frequência de corte). Reduzir α para filtrar mais ruído introduz atraso significativo (defasagem).
Filtro Passa-Baixa de Segunda Ordem
Semelhante a um sistema de suspensão com mola e amortecedor, oferece atenuação mais acentuada (-40dB/dec), sendo cerca de 100 vezes mais eficiente que filtros de primeira ordem para a mesma frequência de corte.
A implementação tipo Butterworth, descrita abaixo, apresenta resposta plana na banda passante, ditsorcendo minimamente os dados originais.
Quando escolher:
- Primeira ordem: Ambientes com recursos computacionais limitados ou ruído moderado.
- Segunda ordem: Sistemas sob alta vibração (ex.: drones, veículos de equilíbrio), como giroscópios e acelerômetros.
Implementação do Filtro de Primeira Ordem (PT1) em C
#ifndef FILTRO_PT1_H
#define FILTRO_PT1_H
#include <stdint.h>
typedef struct {
float valor_saida; // Saída filtrada anterior
float constante_rc; // Constante RC = 1/(2π*fc)
float periodo_amostragem; // Intervalo de tempo entre amostras (segundos)
} filtro_pt1_t;
void inicializar_filtro_pt1(filtro_pt1_t *filtro, uint8_t freq_corte, float dt);
float aplicar_filtro_pt1(filtro_pt1_t *filtro, float entrada);
void reiniciar_filtro_pt1(filtro_pt1_t *filtro, float valor_inicial);
#endif
#include "filtro_pt1.h"
#define PI 3.14159265f
void inicializar_filtro_pt1(filtro_pt1_t *filtro, uint8_t freq_corte, float dt) {
filtro->constante_rc = 1.0f / (2.0f * PI * freq_corte);
filtro->periodo_amostragem = dt;
filtro->valor_saida = 0.0f;
}
float aplicar_filtro_pt1(filtro_pt1_t *filtro, float entrada) {
float alfa = filtro->periodo_amostragem / (filtro->constante_rc + filtro->periodo_amostragem);
filtro->valor_saida = filtro->valor_saida + alfa * (entrada - filtro->valor_saida);
return filtro->valor_saida;
}
void reiniciar_filtro_pt1(filtro_pt1_t *filtro, float valor_inicial) {
filtro->valor_saida = valor_inicial;
}
// Exemplo de uso: suavização de sinal de controle remoto
#include "filtro_pt1.h"
static filtro_pt1_t filtro_roll, filtro_pitch;
void configurar_filtros_controle(void) {
inicializar_filtro_pt1(&filtro_roll, 10, 0.02f); // FC=10Hz, dt=20ms
inicializar_filtro_pt1(&filtro_pitch, 10, 0.02f);
}
void loop_principal(void) {
uint16_t roll_bruto = ler_canal_controle(CANAL_ROLL);
float roll_suavizado = aplicar_filtro_pt1(&filtro_roll, (float)roll_bruto);
definir_atitude_roll(roll_suavizado);
}
Implementação do Filtro de Segunda Ordem (Butterworth) em C
#ifndef FILTRO_SEGUNDA_ORDEM_H
#define FILTRO_SEGUNDA_ORDEM_H
#include <stdint.h>
typedef struct {
float coef_b0, coef_b1, coef_b2; // Coeficientes do numerador
float coef_a1, coef_a2; // Coeficientes do denominador
float estado_anterior_1; // y[n-1]
float estado_anterior_2; // y[n-2]
} filtro_segunda_ordem_t;
void inicializar_filtro_2ordem(filtro_segunda_ordem_t *filtro, float freq_amostragem, float freq_corte);
float aplicar_filtro_2ordem(filtro_segunda_ordem_t *filtro, float amostra);
void reiniciar_filtro_2ordem(filtro_segunda_ordem_t *filtro, float valor);
#endif
#include "filtro_segunda_ordem.h"
#define PI 3.14159265f
#include <math.h>
void inicializar_filtro_2ordem(filtro_segunda_ordem_t *filtro, float freq_amostragem, float freq_corte) {
if (filtro == NULL || freq_corte <= 0.0f) return;
float razao = freq_amostragem / freq_corte;
float omega = tanf(PI / razao);
float denominador = 1.0f + 2.0f * cosf(PI / 4.0f) * omega + omega * omega;
filtro->coef_b0 = (omega * omega) / denominador;
filtro->coef_b1 = 2.0f * filtro->coef_b0;
filtro->coef_b2 = filtro->coef_b0;
filtro->coef_a1 = 2.0f * (omega * omega - 1.0f) / denominador;
filtro->coef_a2 = (1.0f - 2.0f * cosf(PI / 4.0f) * omega + omega * omega) / denominador;
filtro->estado_anterior_1 = 0.0f;
filtro->estado_anterior_2 = 0.0f;
}
float aplicar_filtro_2ordem(filtro_segunda_ordem_t *filtro, float amostra) {
float saida_filtrada = amostra * filtro->coef_b0
+ filtro->estado_anterior_1 * filtro->coef_b1
+ filtro->estado_anterior_2 * filtro->coef_b2;
float novo_estado = saida_filtrada - filtro->estado_anterior_1 * filtro->coef_a1
- filtro->estado_anterior_2 * filtro->coef_a2;
if (!isfinite(novo_estado)) {
novo_estado = amostra;
}
filtro->estado_anterior_2 = filtro->estado_anterior_1;
filtro->estado_anterior_1 = novo_estado;
return saida_filtrada;
}
void reiniciar_filtro_2ordem(filtro_segunda_ordem_t *filtro, float valor) {
float valor_normalizado = valor / (filtro->coef_b0 + filtro->coef_b1 + filtro->coef_b2);
filtro->estado_anterior_1 = valor_normalizado;
filtro->estado_anterior_2 = valor_normalizado;
}
// Exemplo: filtragem de dados de sensores inerciais
#include "filtro_segunda_ordem.h"
static filtro_segunda_ordem_t filtro_giroX, filtro_giroY, filtro_giroZ;
static filtro_segunda_ordem_t filtro_acelX, filtro_acelY, filtro_acelZ;
void inicializar_sensores_filtrados(void) {
inicializar_filtro_2ordem(&filtro_giroX, 1000.0f, 80.0f); // Gyro: 1000Hz, FC=80Hz
inicializar_filtro_2ordem(&filtro_giroY, 1000.0f, 80.0f);
inicializar_filtro_2ordem(&filtro_giroZ, 1000.0f, 80.0f);
inicializar_filtro_2ordem(&filtro_acelX, 1000.0f, 30.0f); // Acel: 1000Hz, FC=30Hz
inicializar_filtro_2ordem(&filtro_acelY, 1000.0f, 30.0f);
inicializar_filtro_2ordem(&filtro_acelZ, 1000.0f, 30.0f);
}
void tarefa_sensores(void) {
float gx_bruto = ler_giroscopio_x();
float gx_filtrado = aplicar_filtro_2ordem(&filtro_giroX, gx_bruto);
// Repetir para outros eixos e acelerômetros
atualizar_imu(gx_filtrado, /* ... */);
}
Seleção de Parâmetros
A escolha da frequência de corte (f_cut) e do coeficiente de suavização (α) depende da aplicação. Para filtros de primeira ordem, α = dt / (RC + dt), onde RC = 1/(2π·f_cut). Filtros de segunda ordem requerem definição da frequência de amostragem e corte para calcular os coeficientes.