MCU & FPGA Filstros,geral Filtragem de ruído em leituras ADC em microcontroladores (ATmega e ESP32) com exemplos em C

Filtragem de ruído em leituras ADC em microcontroladores (ATmega e ESP32) com exemplos em C


Quando você lê um ADC (Conversor Analógico-Digital) no “mundo real”, você não está lendo apenas o sinal útil: você está lendo também ruídos do próprio ADC (quantização, ruído térmico interno, ruído de referência), ruídos de fonte e layout (chaveamento de regulador, retorno de GND, acoplamento de trilhas), e ruídos “de ambiente” (EMI de motores, relés, Wi-Fi, etc.). O resultado clássico é um valor que “dança” alguns LSBs (Least Significant Bits) mesmo com o sinal estável. Filtrar isso em firmware costuma ser o caminho mais barato e rápido, desde que você escolha um filtro compatível com o tipo de ruído e com a dinâmica do seu sinal (se você filtrar demais, mata resposta; se filtrar de menos, sobra ruído).

Um jeito prático de pensar é: primeiro você decide o que quer preservar (variações lentas? degraus rápidos? picos curtíssimos devem sumir?), depois você escolhe um filtro com custo computacional adequado ao MCU e ao seu sampling rate (taxa de amostragem). Em projetos embarcados, isso normalmente vira uma combinação simples e robusta: um filtro de mediana para “estouros” (spikes) + um passa-baixas leve (IIR de 1ª ordem) para suavizar o jitter. A ideia de encapsular a filtragem como um “bloco” bem definido (um filtro como componente) é um padrão recorrente em arquitetura de sistemas embarcados, porque separa aquisição, filtragem e consumo do dado, facilitando testes e evolução do firmware.


O “mínimo que funciona” (pipeline recomendado)

Para a maioria dos sensores analógicos comuns (NTC, shunt com amplificador, potenciômetro, sensores de pressão), um pipeline simples funciona muito bem: você coleta N amostras rápidas, derruba outliers com mediana de 3 ou 5, e em seguida suaviza com um IIR 1-pole. A mediana protege contra picos (por exemplo, interferência curta de comutação), e o IIR dá suavidade com custo baixíssimo e latência controlável.

A seguir eu deixo códigos em C “portáveis”, onde você só precisa plugar sua função de leitura do ADC (no ATmega pode ser adc_read(), no ESP32 pode ser adc1_get_raw()/adc_oneshot_read()).


Filtro de média móvel (FIR simples) — bom para ruído “aleatório”, ruim para degrau rápido

A média móvel é o filtro mais lembrado porque é fácil de entender. Ela reduz ruído branco aproximadamente com ganho de suavização proporcional a √N, mas adiciona latência e “borra” degraus. Se seu sinal muda devagar (ex.: temperatura), ela é ótima.

#include <stdint.h>

typedef struct {
    uint32_t acc;
    uint16_t *buf;
    uint16_t size;
    uint16_t idx;
    uint8_t  primed;
} movavg_t;

void movavg_init(movavg_t *f, uint16_t *buffer, uint16_t size) {
    f->acc = 0;
    f->buf = buffer;
    f->size = size;
    f->idx = 0;
    f->primed = 0;
    for (uint16_t i = 0; i < size; i++) f->buf[i] = 0;
}

uint16_t movavg_update(movavg_t *f, uint16_t x) {
    f->acc -= f->buf[f->idx];
    f->buf[f->idx] = x;
    f->acc += x;

    f->idx++;
    if (f->idx >= f->size) {
        f->idx = 0;
        f->primed = 1;
    }

    // Durante o aquecimento, a média fica "puxada" para zero.
    // Você pode tratar isso retornando x até primed=1, se preferir.
    uint16_t denom = f->primed ? f->size : (f->idx == 0 ? 1 : f->idx);
    return (uint16_t)(f->acc / denom);
}

No ATmega, isso é leve, mas se você usar janelas grandes (ex.: 64, 128) em uma taxa alta, vira custo de RAM e latência. No ESP32, geralmente é tranquilo.


Filtro IIR de 1ª ordem (passa-baixas “exponencial”) — o cavalo de batalha do firmware

Esse filtro é o “suavizador” mais usado em embarcados porque tem custo O(1), pouca RAM e é estável se você escolher o ganho corretamente. Ele implementa, na prática, uma média ponderada: o valor novo puxa o estado aos poucos. Em tempo discreto: y[n] = y[n-1] + α (x[n] - y[n-1]). O parâmetro α controla o compromisso entre suavidade e resposta.

Versão fixed-point (boa para ATmega, sem float)

Aqui eu uso Q15 (15 bits fracionários). alpha_q15 vai de 1 a 32767, equivalente a (0, 1).

#include <stdint.h>

typedef struct {
    int32_t y_q15;       // estado em Q15
    uint16_t alpha_q15;  // 0..32767 (~0..1)
    uint8_t initialized;
} iir1_q15_t;

void iir1_q15_init(iir1_q15_t *f, uint16_t alpha_q15) {
    if (alpha_q15 > 32767) alpha_q15 = 32767;
    f->y_q15 = 0;
    f->alpha_q15 = alpha_q15;
    f->initialized = 0;
}

uint16_t iir1_q15_update(iir1_q15_t *f, uint16_t x) {
    int32_t x_q15 = ((int32_t)x) << 15;

    if (!f->initialized) {
        f->y_q15 = x_q15;
        f->initialized = 1;
        return x;
    }

    // y = y + alpha*(x - y)
    int32_t err = x_q15 - f->y_q15;
    f->y_q15 += ( (int32_t)f->alpha_q15 * err ) >> 15;

    // arredondamento simples e saturação para 16 bits
    int32_t y = (f->y_q15 + (1 << 14)) >> 15;
    if (y < 0) y = 0;
    if (y > 65535) y = 65535;
    return (uint16_t)y;
}

Como escolher α sem virar matemática demais? Se você amostra a cada Ts e quer uma suavização equivalente a uma constante de tempo aproximada τ, uma aproximação prática é α ≈ Ts / (τ + Ts). Exemplo: amostragem a 1 kHz (Ts=1 ms) e você quer “assentar” em ~100 ms ⇒ α ≈ 0,001/(0,101) ≈ 0,0099, então alpha_q15 ≈ 0,0099 * 32768 ≈ 324. Isso dá um filtro bem suave.


Filtro de mediana — mata spikes sem “atrasar” tanto quanto média

Se o seu problema são picos curtos (ex.: ruído de chaveamento de relé, PWM, comutação de fonte), a mediana é muito eficiente. Para janela 3, o custo é mínimo.

#include <stdint.h>

static inline uint16_t median3_u16(uint16_t a, uint16_t b, uint16_t c) {
    // ordena parcialmente sem usar array
    if (a > b) { uint16_t t=a; a=b; b=t; }
    if (b > c) { uint16_t t=b; b=c; c=t; }
    if (a > b) { uint16_t t=a; a=b; b=t; }
    return b; // b é a mediana
}

typedef struct {
    uint16_t x1, x2;
    uint8_t primed;
} median3_t;

void median3_init(median3_t *f) {
    f->x1 = f->x2 = 0;
    f->primed = 0;
}

uint16_t median3_update(median3_t *f, uint16_t x) {
    if (!f->primed) {
        f->x2 = f->x1;
        f->x1 = x;
        if (f->x2 != 0) f->primed = 1;
        return x;
    }
    uint16_t y = median3_u16(f->x2, f->x1, x);
    f->x2 = f->x1;
    f->x1 = x;
    return y;
}

Exemplo completo: mediana + IIR (bom “default” para ATmega e ESP32)

Aqui fica o esqueleto de uma função de “leitura filtrada” que você pode usar em uma task/loop periódico. Você substitui adc_read_raw() por sua leitura real.

#include <stdint.h>

// ---- mocks / stubs: substitua pela sua HAL ----
uint16_t adc_read_raw(void); // ATmega: ADC; ESP32: adc oneshot raw
// ----------------------------------------------

typedef struct {
    median3_t   med;
    iir1_q15_t  lp;
} adc_filter_t;

void adc_filter_init(adc_filter_t *f, uint16_t alpha_q15) {
    median3_init(&f->med);
    iir1_q15_init(&f->lp, alpha_q15);
}

uint16_t adc_read_filtered(adc_filter_t *f) {
    uint16_t x = adc_read_raw();
    uint16_t x_med = median3_update(&f->med, x);
    uint16_t y = iir1_q15_update(&f->lp, x_med);
    return y;
}

No ATmega, isso costuma caber confortavelmente e resolve 80% dos casos reais. No ESP32, você pode manter igual ou trocar o IIR para float se quiser ajustar α dinamicamente com mais facilidade, mas não é necessário.


Quando isso não basta (e o que fazer)

Se o seu ruído é “bem comportado” (aleatório, de alta frequência), média móvel e IIR resolvem. Se o ruído é dominado por rede elétrica (50/60 Hz) acoplando no sensor, muitas vezes vale mais a pena atacar na causa (impedância de fonte, RC analógico antes do ADC, referência e aterramento) e depois, no firmware, usar um filtro que tenha rejeição nessa frequência, como um notch discreto ou uma média sincronizada com o período (quando você sabe a fase). E se o ADC está sofrendo com fonte de alta impedância, nenhum filtro compensa totalmente: aí você melhora o circuito (buffer com op-amp, capacitor no sample/hold, tempo de aquisição maior, etc.) antes de “pedir milagre” ao DSP.

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

Falhas em Antenas Cerâmicas e Componentes Rígidos em Ambientes com Vibração: Causas, Diagnóstico e SoluçõesFalhas em Antenas Cerâmicas e Componentes Rígidos em Ambientes com Vibração: Causas, Diagnóstico e Soluções

Antenas cerâmicas e outros componentes rígidos apresentam alta taxa de falhas quando utilizados em equipamentos sujeitos a vibração, como máquinas industriais, veículos pesados e drones. Esses componentes, por serem frágeis

0
Adoraria saber sua opinião, comente.x