MCU & FPGA DSP Filtro Digital com Séries de Taylor e Cepstrum para Detecção de Assobio em RP2040

Filtro Digital com Séries de Taylor e Cepstrum para Detecção de Assobio em RP2040


6 — Robustez “de produção”: janela (Hann), gate de silêncio, suavização temporal e integração de periféricos no RP2040

Nesta seção vamos resolver os problemas típicos que aparecem quando você sai do “laboratório silencioso” e vai para um ambiente real:

  1. Leakage espectral (vazamento) no frame → melhora com janela Hann.
  2. Falsos positivos em ruído / vento / batida → melhora com gate por energia (RMS).
  3. LED “tremendo” com pequenas variações → melhora com suavização temporal (EMA) e histerese.
  4. Amarrar tudo com inicialização de ADC, clock de amostragem e GPIO no Pico SDK.

Importante: ainda não vou despejar o “arquivo único final” aqui; vamos encaixando as peças, e na próxima seção eu entrego o código completo e funcional (um main.c pronto para compilar).


6.1 Janela Hann (por que e como)

Quando você pega um frame de 256 amostras, você está implicitamente “cortando” um trecho do sinal. Esse corte gera descontinuidade nas bordas, o que espalha energia para bins vizinhos na FFT (leakage).

A janela Hann é:

\[
w[n] = 0.5 \Bigl(1 – \cos(\frac{2\pi n}{N-1})\Bigr)
\]

Aplicação:

\[
x_w[n] = x[n]\cdot w[n]
\]

6.1.1 Tabela da janela (pré-calculada)

static float g_hann[FRAME_N];

static void window_init_hann(void)
{
    for (int n = 0; n < FRAME_N; n++) {
        float a = 2.0f * (float)M_PI * (float)n / (float)(FRAME_N - 1);
        g_hann[n] = 0.5f * (1.0f - cosf(a));
    }
}

6.1.2 Aplicar janela antes da FFT

No compute_cepstrum_and_detect() (seção anterior), troque o carregamento do frame por:

for (int i = 0; i < FRAME_N; i++) {
    buf[i].re = frame[i] * g_hann[i];
    buf[i].im = 0.0f;
}

6.2 Gate de silêncio por energia RMS (para cortar falsos positivos)

Se o ambiente está silencioso, o cepstrum pode “inventar” picos pequenos por ruído numérico. A solução clássica é não rodar detecção quando a energia do frame é muito baixa.

RMS:

\[
\text{RMS} = \sqrt{\frac{1}{N}\sum_{n=0}^{N-1} x[n]^2}
\]

Como queremos eficiência, usamos energia média sem sqrt:

\[
E = \frac{1}{N}\sum x[n]^2
\]

6.2.1 Função de energia do frame

static float frame_energy(const float *x)
{
    float acc = 0.0f;
    for (int i = 0; i < FRAME_N; i++) {
        acc += x[i] * x[i];
    }
    return acc / (float)FRAME_N;
}

6.2.2 Usar o gate antes do cepstrum

static bool process_frame(frame_buf_t *fb, uint led_gpio)
{
    if (!fb->full) return false;
    fb->full = false;

    float E = frame_energy(fb->data);

    // Ajuste inicial: energia mínima para “vale a pena analisar”
    const float E_MIN = 0.0008f; // você calibra depois

    if (E < E_MIN) {
        // ambiente silencioso: tende a desligar LED com histerese
        // aqui podemos forçar "peak baixo" para cair pelo TH_OFF
        whistle_update_led(0.0f, 10, led_gpio);
        return true;
    }

    int q = 0;
    float peak = compute_cepstrum_and_detect(fb->data, &q);
    whistle_update_led(peak, q, led_gpio);
    return true;
}

6.3 Suavização temporal (EMA) do “peak” do cepstrum

O pico do cepstrum é uma medida ruidosa frame-a-frame. Uma suavização leve resolve.

EMA (Exponential Moving Average):

\[
p_f[n] = \alpha p_f[n-1] + (1-\alpha)p[n]
\]

  • \(\alpha\) perto de 1 → mais suave, responde mais lento
  • \(\alpha\) menor → mais rápido, mais nervoso

6.3.1 Aplicando EMA dentro do detector

Adicione um estado global:

static float g_peak_filt = 0.0f;

E no processamento do frame:

float peak = compute_cepstrum_and_detect(fb->data, &q);

// suaviza
const float ALPHA = 0.85f;
g_peak_filt = ALPHA * g_peak_filt + (1.0f - ALPHA) * peak;

whistle_update_led(g_peak_filt, q, led_gpio);

Agora os limiares TH_ON/TH_OFF ficam bem mais estáveis.


6.4 Amostragem estável no RP2040 (sem “sleep torto”)

sleep_us(1000000/FS) funciona, mas tem jitter. Para deixar mais “engenharia de verdade”, usamos timer repetitivo (Pico SDK) para chamar a rotina de amostragem com período fixo.

6.4.1 Callback de amostragem

#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "pico/time.h"

static repeating_timer_t g_timer;

static taylor_filter_t g_filter = {0};
static frame_buf_t     g_frame;

static inline float read_adc_normalized(void)
{
    uint16_t raw = adc_read(); // 0..4095
    return ((float)raw - 2048.0f) / 2048.0f;
}

static bool sample_timer_cb(repeating_timer_t *rt)
{
    (void)rt;

    float x = read_adc_normalized();
    float y = taylor_bandpass_step(&g_filter, x);
    frame_buf_push(&g_frame, y);

    return true; // keep repeating
}

6.5 Inicialização do ADC e do LED (Pico SDK)

Aqui é onde você amarra com o hardware real. Exemplo típico:

  • ADC do RP2040: entradas ADC0..ADC3 (GPIO 26..29)
  • Vamos supor microfone/saída analógica no ADC0 (GPIO26)
  • LED em algum GPIO (ex.: o LED onboard do Pico é 25; na BitDogLab pode variar — você ajusta o define)
#define ADC_GPIO   28
#define ADC_CH     0
#define LED_GPIO   13

static void hw_init(void)
{
    stdio_init_all();

    // LED
    gpio_init(LED_GPIO);
    gpio_set_dir(LED_GPIO, GPIO_OUT);
    gpio_put(LED_GPIO, 0);

    // ADC
    adc_init();
    adc_gpio_init(ADC_GPIO);
    adc_select_input(ADC_CH);
}

6.6 Inicialização “DSP”: twiddles, janela e buffers

static void dsp_init_all(void)
{
    frame_buf_init(&g_frame);
    fft_init_twiddles();
    window_init_hann();
    g_peak_filt = 0.0f;
}

6.7 Main “quase final”: timer coleta, loop processa frames

int main(void)
{
    hw_init();
    dsp_init_all();

    // agenda amostragem a cada 1/FS segundos
    const int sample_period_us = 1000000 / FS_HZ;
    add_repeating_timer_us(-sample_period_us, sample_timer_cb, NULL, &g_timer);

    while (true) {
        // processa quando frame fecha
        process_frame(&g_frame, LED_GPIO);

        // o loop pode dormir um pouco; a amostragem continua no timer
        sleep_ms(1);
    }
}

Esse “main” já fecha o sistema:

  • Timer garante amostragem estável
  • Frame fecha em 256 amostras (~32 ms em 8 kHz)
  • Cepstrum roda ~31 vezes por segundo
  • LED aciona por detecção robusta

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

LPCC (Linear Prediction Cepstral Coefficients): Fundamentos, Algoritmos e Aplicações em Sistemas EmbarcadosLPCC (Linear Prediction Cepstral Coefficients): Fundamentos, Algoritmos e Aplicações em Sistemas Embarcados

Os coeficientes cepstrais por predição linear (LPCC) são uma técnica clássica e altamente eficiente para extração de características em sinais de fala, vibração e acústica industrial. Neste artigo, apresentamos uma

0
Adoraria saber sua opinião, comente.x