Pacote “copiável”: árvore de arquivos, CMakeLists.txt, main.c consolidado e calibração prática
Abaixo vai um esqueleto de projeto Zephyr organizado para você conseguir evoluir rápido (sem misturar demais “HAL do ESP-IDF” com a aplicação).
6.1 Árvore de arquivos sugerida
zephyr-esp32-adc-vad-dsp/
├─ CMakeLists.txt
├─ prj.conf
├─ boards/
│ └─ esp32s3_devkitc.overlay (ajuste para sua board)
└─ src/
├─ main.c
├─ app_stream.h
├─ adc_dma_producer.c
├─ adc_dt_map_esp32.h
├─ consumer_dsp_uart.c
└─ dsp_goertzel.c
6.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(esp32_adc_vad_dsp)
target_sources(app PRIVATE
src/main.c
src/adc_dma_producer.c
src/consumer_dsp_uart.c
src/dsp_goertzel.c
)
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
6.3 main.c consolidado (2 threads reais)
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/adc.h>
#include "app_stream.h"
/* ===== DeviceTree: nó /app com io-channels ===== */
#define APP_NODE DT_PATH(app)
#if !DT_NODE_EXISTS(APP_NODE)
#error "Nó /app não existe. Confira boards/<sua_board>.overlay"
#endif
/* Disponibiliza adc_spec global para o produtor */
const struct adc_dt_spec adc_spec = ADC_DT_SPEC_GET_BY_IDX(APP_NODE, 0);
/* ===== IPC: slab + msgq ===== */
K_MEM_SLAB_DEFINE(frame_slab, sizeof(struct stream_frame), 12, 4);
K_MSGQ_DEFINE(stream_q, sizeof(struct stream_msg), 16, 4);
/* ===== Threads ===== */
#define PRODUCER_STACK_SZ 4096
#define CONSUMER_STACK_SZ 6144
#define PRODUCER_PRIO 3
#define CONSUMER_PRIO 4
K_THREAD_STACK_DEFINE(producer_stack, PRODUCER_STACK_SZ);
K_THREAD_STACK_DEFINE(consumer_stack, CONSUMER_STACK_SZ);
static struct k_thread producer_thread_data;
static struct k_thread consumer_thread_data;
/* Threads reais */
extern void adc_stream_thread(void *p1, void *p2, void *p3);
extern void dsp_uart_thread(void *p1, void *p2, void *p3);
int main(void)
{
printk("\nZephyr+ESP32: ADC continuous + VAD + Producer/Consumer + Fdom\n");
if (!device_is_ready(adc_spec.dev)) {
printk("ADC device not ready\n");
return 0;
}
k_thread_create(&producer_thread_data, producer_stack,
K_THREAD_STACK_SIZEOF(producer_stack),
adc_stream_thread, NULL, NULL, NULL,
PRODUCER_PRIO, 0, K_NO_WAIT);
k_thread_create(&consumer_thread_data, consumer_stack,
K_THREAD_STACK_SIZEOF(consumer_stack),
dsp_uart_thread, NULL, NULL, NULL,
CONSUMER_PRIO, 0, K_NO_WAIT);
return 0;
}
6.4 app_stream.h (com parâmetros DSP + mensagens)
#pragma once
#include <zephyr/kernel.h>
#include <stdint.h>
/* ===== Pipeline messages ===== */
enum stream_msg_type {
STREAM_MSG_AUDIO_FRAME = 1,
STREAM_MSG_VOICE_START = 2,
STREAM_MSG_VOICE_END = 3,
};
#define ADC_FRAME_SAMPLES 256
struct stream_frame {
int16_t samples[ADC_FRAME_SAMPLES];
uint16_t n;
uint32_t t_ms;
};
struct stream_msg {
enum stream_msg_type type;
struct stream_frame *frame;
};
/* ===== DSP config ===== */
#define DSP_WIN_N 1024u
#define DSP_FS_HZ 16000u
#define DSP_FMIN_HZ 80u
#define DSP_FMAX_HZ 2000u
#define DSP_FSTEP_HZ 20u
#define DSP_ENERGY_MIN 800u
6.5 Build/flash (exemplo)
west build -p always -b esp32s3_devkitc .
west flash
west monitor
(Use a board correta: esp32_devkitc_wroom, esp32c3_devkitm, esp32p4_*, etc.)
6.6 Calibração prática: como fazer “funcionar de primeira” sem ficar caçando fantasma
1) Confirme a taxa real de amostragem
Se DSP_FS_HZ não bater com o ADC, sua frequência sai errada (ex.: tudo 10% deslocado).
- Garanta que
ADC_SAMPLE_RATE_HZno produtor eDSP_FS_HZno consumidor sejam o mesmo número. - Se você suspeitar que o ADC está entregando outra taxa, injete um tom conhecido (ex.: 440 Hz) e veja se o pico reportado bate.
2) Ajuste o VAD simples (threshold + hangover)
VAD_RMS_THRESHOLD: começa alto (para evitar falso positivo), depois abaixa até detectar fala.VAD_HANGOVER_FRAMES: aumenta se o stream estiver “quebrando” em múltiplos pedaços durante uma frase.
Regra de bolso:
- threshold alto demais → nunca entra em
VOICE_START - threshold baixo demais → pega ruído/ventilador e nunca sai do stream
3) Ajuste DSP_ENERGY_MIN
Ele evita imprimir frequência quando o trecho é quase silêncio (mesmo dentro do hangover).
4) Ajuste resolução x custo (DSP_FSTEP_HZ)
- 10 Hz: melhor resolução, mais CPU
- 20–25 Hz: bom equilíbrio
- 50 Hz: muito leve, mas pode “pular” o pico
5) Se a frequência “dançar” muito
- habilite overlap 50% (seção 5)
- aumente
DSP_WIN_N(ex.: 2048) para mais estabilidade (com mais latência) - ou estreite a faixa (
DSP_FMIN..DSP_FMAX) se você já sabe onde está o pico esperado