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

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_HZ no produtor e DSP_FS_HZ no 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

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

0
Adoraria saber sua opinião, comente.x