MCU & FPGA lwIP,RTOS Zephyr no ESP32: ADC Contínuo com DMA, VAD e Processamento de Sinais em Tempo Real

Zephyr no ESP32: ADC Contínuo com DMA, VAD e Processamento de Sinais em Tempo Real

Robustez em campo: prioridades, backpressure (slab/msgq), telemetria e VAD com histerese

Quando você sai do “demo de bancada” e vai para um firmware real, os problemas aparecem sempre nos mesmos lugares: perda de amostras, jitter, buffers lotando e VAD oscilando (entra/sai do stream o tempo todo). Esta seção é para deixar o pipeline “resistente” e fácil de diagnosticar.


7.1 Prioridades e latência: o produtor precisa ser “quase intocável”

A regra prática:

  • Produtor (ADC contínuo): prioridade mais alta (número menor no Zephyr), porque ele está colado no fluxo de dados.
  • Consumidor (DSP/UART): prioridade abaixo, porque DSP pode atrasar um pouco; perder amostra é pior.
  • UART/LOG: se você imprimir demais no caminho do produtor, você cria jitter. O produtor deve logar raramente.

Exemplo típico:

#define PRODUCER_PRIO 2
#define CONSUMER_PRIO 4

E mais importante: nunca faça FFT/Goertzel no produtor. O produtor só:

  1. lê do driver contínuo,
  2. empacota frame,
  3. roda VAD leve,
  4. envia para fila.

7.2 Backpressure: quando slab ou msgq enchem, você precisa saber “por quê”

Na prática, vai acontecer de algum buffer encher (principalmente em debug). O erro é “silenciar” isso.

Crie uma estrutura de telemetria global:

/* src/telemetry.h */
#pragma once
#include <stdint.h>

struct stream_telemetry {
    uint32_t adc_reads;
    uint32_t frames_alloc_ok;
    uint32_t frames_alloc_fail;
    uint32_t msgq_put_ok;
    uint32_t msgq_put_fail;
    uint32_t vad_start;
    uint32_t vad_end;
    uint32_t frames_dropped_silence;
    uint32_t frames_dropped_backpressure;
    uint32_t consumer_frames;
    uint32_t consumer_windows;
};

extern volatile struct stream_telemetry g_tlm;

Defina em um .c:

/* src/telemetry.c */
#include "telemetry.h"
volatile struct stream_telemetry g_tlm;

Agora, no produtor (adc_dma_producer.c), incremente contadores nos pontos críticos:

#include "telemetry.h"

/* ... dentro do loop ... */
g_tlm.adc_reads++;

if (k_mem_slab_alloc(&frame_slab, (void **)&frame, K_NO_WAIT) != 0) {
    g_tlm.frames_alloc_fail++;
    g_tlm.frames_dropped_backpressure++;
    continue;
}
g_tlm.frames_alloc_ok++;

/* silêncio antes do stream ativo */
if (!s_voice_active && !vad_now) {
    g_tlm.frames_dropped_silence++;
    k_mem_slab_free(&frame_slab, (void *)frame);
    continue;
}

/* eventos VAD */
if (!s_voice_active && vad_now) {
    g_tlm.vad_start++;
    /* ... */
}
if (s_voice_active && end_condition) {
    g_tlm.vad_end++;
    /* ... */
}

if (k_msgq_put(&stream_q, &msg, K_NO_WAIT) != 0) {
    g_tlm.msgq_put_fail++;
    g_tlm.frames_dropped_backpressure++;
    k_mem_slab_free(&frame_slab, (void *)frame);
} else {
    g_tlm.msgq_put_ok++;
}

No consumidor:

g_tlm.consumer_frames++;
/* quando processar uma janela DSP: */
g_tlm.consumer_windows++;

7.3 Uma “task” leve de diagnóstico (telemetria na UART sem travar o pipeline)

Você não quer imprimir em toda iteração do consumidor, muito menos do produtor. O ideal é ter uma terceira thread (bem baixa prioridade) que imprime um resumo a cada 1s.

/* src/diag_thread.c */
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include "telemetry.h"

void diag_thread(void *p1, void *p2, void *p3)
{
    (void)p1; (void)p2; (void)p3;

    while (1) {
        k_sleep(K_SECONDS(1));

        printk("TLM: adc_reads=%u alloc_ok=%u alloc_fail=%u msg_ok=%u msg_fail=%u "
               "vad_start=%u vad_end=%u drop_sil=%u drop_bp=%u cons_frames=%u cons_win=%u\n",
               g_tlm.adc_reads,
               g_tlm.frames_alloc_ok, g_tlm.frames_alloc_fail,
               g_tlm.msgq_put_ok, g_tlm.msgq_put_fail,
               g_tlm.vad_start, g_tlm.vad_end,
               g_tlm.frames_dropped_silence, g_tlm.frames_dropped_backpressure,
               g_tlm.consumer_frames, g_tlm.consumer_windows);
    }
}

Esse “painel” te diz rapidamente:

  • se o consumidor está atrasando (alloc_fail/msg_fail sobem),
  • se o VAD está “quebrando” stream (vad_start/vad_end muito frequente),
  • se você está descartando silêncio em excesso (talvez threshold alto demais).

7.4 VAD por energia com histerese: reduz “chattering” (entra/sai toda hora)

O VAD simples de um threshold único é sensível: ruído perto do limiar faz o estado oscilar.

A solução clássica é histerese:

  • THR_ON (mais alto) para entrar no stream,
  • THR_OFF (mais baixo) para sair do stream.

Além disso, mantenha hangover para não cortar palavras.

Exemplo (substitui a lógica do produtor):

#define VAD_THR_ON        1400u
#define VAD_THR_OFF       900u
#define VAD_HANGOVER_FR   12

static bool s_voice_active;
static int  s_hang;

static void vad_update(uint32_t e_absmean)
{
    if (!s_voice_active) {
        if (e_absmean >= VAD_THR_ON) {
            s_voice_active = true;
            s_hang = VAD_HANGOVER_FR;
            g_tlm.vad_start++;
            emit_vad_event(STREAM_MSG_VOICE_START);
        }
        return;
    }

    /* já ativo */
    if (e_absmean >= VAD_THR_OFF) {
        s_hang = VAD_HANGOVER_FR; /* renova */
        return;
    }

    /* caiu abaixo do THR_OFF */
    s_hang--;
    if (s_hang <= 0) {
        s_voice_active = false;
        g_tlm.vad_end++;
        emit_vad_event(STREAM_MSG_VOICE_END);
    }
}

Com isso, o VAD fica muito mais estável em ambientes reais.


7.5 Diagnóstico de “perda de dados”: como interpretar os sintomas

Se você observar:

  • frames_alloc_fail subindo: slab pequeno demais ou consumidor lento demais.
    Ações: aumentar frame_slab (número de blocos), reduzir custo do DSP (passo maior, faixa menor), reduzir prints.
  • msgq_put_fail subindo: fila pequena demais ou consumidor não está drenando.
    Ações: aumentar profundidade da k_msgq, checar prioridade do consumidor, checar travas no consumidor.
  • VAD “metralhando” start/end: threshold ruim e/ou sem histerese/hangover.
    Ações: histerese + hangover, calibrar THR_ON/OFF.
  • Frequência errada/deslocada: DSP_FS_HZ não bate com taxa real.
    Ações: validar com tom conhecido e ajustar sample_freq_hz.

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

FreeRTOS na Prática: Threads, Semáforos, Filas, Mutexes, Timers e Boas Práticas em Sistemas EmbarcadosFreeRTOS na Prática: Threads, Semáforos, Filas, Mutexes, Timers e Boas Práticas em Sistemas Embarcados

Este artigo faz parte de uma série técnica semanal sobre FreeRTOS e apresenta, de forma didática e rigorosa, os principais conceitos de concorrência e sincronização em sistemas embarcados. São explicados

0
Adoraria saber sua opinião, comente.x