Table of Contents
- Visão geral da solução no Zephyr para ESP32: ADC contínuo (DMA), VAD e pipeline produtor/consumidor
- DeviceTree no Zephyr (ESP32): configurando ADC e o “nó da aplicação” para io-channels, + base do projeto
- Aquisição contínua “tipo DMA” no ESP32 (ADC continuous mode), VAD delimitando stream e envio para o consumidor
- Consumidor: estimando a frequência principal do stream e imprimindo na UART
- Fechando o projeto: mapeando ADC pelo DeviceTree, janela deslizante com overlap e Goertzel em ponto fixo (Q31)
- Pacote “copiável”: árvore de arquivos, CMakeLists.txt, main.c consolidado e calibração prática
- Robustez em campo: prioridades, backpressure (slab/msgq), telemetria e VAD com histerese
- 7.1 Prioridades e latência: o produtor precisa ser “quase intocável”
- 7.2 Backpressure: quando slab ou msgq enchem, você precisa saber “por quê”
- 7.3 Uma “task” leve de diagnóstico (telemetria na UART sem travar o pipeline)
- 7.4 VAD por energia com histerese: reduz “chattering” (entra/sai toda hora)
- 7.5 Diagnóstico de “perda de dados”: como interpretar os sintomas
- VAD em hardware (ESP32-P4) vs VAD em software + Conclusão e SEO
Visão geral da solução no Zephyr para ESP32: ADC contínuo (DMA), VAD e pipeline produtor/consumidor
Quando você tenta fazer “captura de ruidos como da magnectostricção” a partir de ADC no ESP32 com o Zephyr, aparecem três problemas clássicos ao mesmo tempo:
- aquisição contínua em alta taxa (idealmente com DMA para não “matar” a CPU),
- delimitação de trechos úteis (detectar início/fim de fala para não processar silêncio), e
- processamento em tempo real (calcular alguma métrica — aqui, a frequência principal — e jogar na UART).
A arquitetura mais robusta para isso, no mundo de sistemas em tempo real, é separar responsabilidades em threads com um canal bem definido de troca de dados (fila/ring buffer), exatamente o tipo de organização que livros de padrões para sistemas em tempo real tratam como base de escalabilidade e diagnósticos previsíveis.
O ponto crítico: “Zephyr puro” vs “driver contínuo do ESP-IDF”
O Zephyr tem um caminho bem canônico para ADC via DeviceTree + ADC API (por exemplo o sample adc_dt) (docs.zephyrproject.org) e possui binding específico para ADC do ESP32 (espressif,esp32-adc) (docs.zephyrproject.org). Isso resolve bem leituras “por amostra” e configurações padrão.
Só que voz por ADC normalmente pede stream contínuo, e no ecossistema Espressif a forma “oficial” para isso é o ADC Continuous Mode driver, que foi desenhado para conversões contínuas e leitura de resultados em buffer (tipicamente alimentado por DMA) (Espressif Systems). Na prática, para cumprir o requisito (“leitura contínua por DMA do ADC”), o caminho mais realista é:
- usar Zephyr como RTOS (threads, filas, logging/uart), mas
- usar o driver de ADC contínuo do ESP-IDF por baixo (como “módulo”/componente), expondo para o Zephyr uma API de aquisição contínua.
Isso evita reinventar o mecanismo de aquisição contínua e te dá taxas estáveis.
VAD no ESP32: duas realidades (hardware x software)
Aqui há uma nuance importante:
- ESP32-P4 tem módulo VAD em hardware documentado pela Espressif (Espressif Systems). Nesse caso, faz sentido “delimitar stream” com evento/flag do próprio periférico.
- Em vários outros ESP32 (ex.: ESP32 clássico, S3, etc.), é comum usar VAD por software vindo do ecossistema de áudio/speech da Espressif (ex.: ESP-SR/ADF). Há documentação de VAD em software no contexto dessas libs (espressif-docs.readthedocs-hosted.com).
Eu vou estruturar como se fosse uma pipeline genérica onde o “VAD” é um bloco que produz eventos VOICE_START/VOICE_END. A implementação desse bloco pode ser:
- hardware VAD (se for ESP32-P4), ou
- software VAD (ESP-SR/ADF) quando o chip não oferecer o periférico.
Pipeline proposta (alto nível)
A solução fica muito limpa com duas threads e um “canal” entre elas:
- Thread Produtora (adc_stream_thread)
- Inicializa ADC contínuo (driver do ESP-IDF) e começa a receber “frames” (blocos) de amostras.
- Alimenta o bloco de VAD (hardware ou software).
- Quando o VAD indicar fala ativa, empacota blocos de amostras e envia para a fila/ring buffer do consumidor.
- Quando indicar fim da fala, envia um “marcador” (metadado) de fim de stream.
- Thread Consumidora (dsp_uart_thread)
- Recebe blocos do stream “recortado” pelo VAD.
- Acumula um buffer mínimo (janela) e calcula a frequência principal (pitch/peak espectral) e imprime na UART.
Essa separação é a base para desempenho e previsibilidade: a thread de aquisição não fica “presa” em FFT/Goertzel; a thread de DSP não perde amostra porque alguém resolveu imprimir logs demais.
Onde o DeviceTree entra (mesmo usando ADC contínuo do ESP-IDF)
Mesmo que o “motor” de aquisição contínua venha do ESP-IDF, você ainda quer o DeviceTree como fonte única de verdade para:
- qual ADC está ativo,
- quais canais/pinos estão habilitados,
- ganho/atenuação/resolução,
- e a UART de saída (console).
O Zephyr já tem binding do ADC do ESP32 (docs.zephyrproject.org) e o modelo de amostragem por DT é bem estabelecido no sample adc_dt (docs.zephyrproject.org). A ideia prática é: o DT descreve os canais, e o o código traduz isso para a configuração do driver contínuo.
Na próxima seção), eu vou te entregar:
- um overlay
.overlayde exemplo configurando ADC e um “nó” de aplicação comio-channels, - o esqueleto completo das duas threads (produtor/consumidor) com filas,
- e o desenho do contrato de mensagens (bloco de amostras + eventos VAD).