DoA y VAD reSpeaker Flex con Xiao ESP32S3
Introducción
El ReSpeaker Flex es una solución de procesamiento de voz con múltiples micrófonos basada en el XMOS XVF3800, diseñada para aplicaciones embebidas que requieren inteligencia de audio en tiempo real. El XVF3800 calcula internamente la Detección de Actividad de Voz (VAD) y la Dirección de Llegada (DoA) para indicar cuándo hay voz presente y desde qué dirección se origina. Estos resultados de inferencia pueden ser accedidos directamente por un MCU host a través de I2C usando comandos basados en recursos, lo que habilita funciones como activación por voz, localización de fuentes de sonido y retroalimentación consciente de la dirección sin procesar audio en bruto en el host.
| reSpeaker Flex XVF3800 Lineal con XIAO ESP32S3 | reSpeaker Flex XVF3800 Circular con XIAO ESP32S3 | |
|---|---|---|
![]() | ![]() | |
El firmware que admite este código es respeaker_xvf3800_i2s_master_dfu_firmware_v1.0.x_48k_test5.bin. Así que asegúrate de flashearlo primero
Código de Arduino
#include <Wire.h>
#include "AudioTools.h"
// ── I2C ──────────────────────────────────────────────────────────
#define XMOS_ADDR 0x2C
#define GPO_SERVICER_RESID 20 // resid
#define GPO_SERVICER_RESID_DOA 18 // cmdid — fixed from 19 to 18 (matches Python)
#define GPO_DOA_READ_NUM_BYTES 4
// ── I2S pins (XIAO ESP32S3 ↔ ReSpeaker Flex) ─────────────────────
#define I2S_BCK_PIN 8
#define I2S_WS_PIN 7
#define I2S_DATA_PIN 43 // RX from XVF3800
// ── Audio config ──────────────────────────────────────────────────
const int SAMPLE_RATE = 16000;
const int CHANNELS = 2; // stereo interleaved from XVF3800
const int BITS = 32;
const int BLOCK_SAMPLES = 512; // samples per chunk (both channels)
AudioInfo audioInfo(SAMPLE_RATE, CHANNELS, BITS);
I2SStream i2s;
// ── Timing ────────────────────────────────────────────────────────
unsigned long lastDoaMs = 0;
const unsigned long DOA_INTERVAL_MS = 250;
// ─────────────────────────────────────────────────────────────────
bool read_doa(uint16_t &doa_angle, uint16_t &speech) {
Wire.beginTransmission(XMOS_ADDR);
Wire.write(GPO_SERVICER_RESID);
Wire.write(GPO_SERVICER_RESID_DOA | 0x80); // read flag
Wire.write(GPO_DOA_READ_NUM_BYTES + 1); // +1 for status byte
if (Wire.endTransmission() != 0) return false;
uint8_t total = GPO_DOA_READ_NUM_BYTES + 1;
Wire.requestFrom(XMOS_ADDR, total);
if (Wire.available() < total) return false;
uint8_t status = Wire.read(); // status byte (0x00 = OK)
uint8_t buf[4];
for (uint8_t i = 0; i < GPO_DOA_READ_NUM_BYTES; i++) buf[i] = Wire.read();
// Match Python: DOA = result[1] + result[2]*256
// SPEECH = result[3]
doa_angle = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
speech = (uint16_t)buf[2] | ((uint16_t)buf[3] << 8);
return true;
}
// RMS on left channel only (stride 2 over stereo interleaved buffer)
float compute_rms(int32_t *samples, int total_samples) {
double sum = 0;
int count = 0;
for (int i = 0; i < total_samples; i += 2) {
double s = (double)samples[i] / (double)INT32_MAX;
sum += s * s;
count++;
}
return count > 0 ? (float)sqrt(sum / count) : 0.0f;
}
// ─────────────────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
while (!Serial);
Wire.begin();
delay(500);
I2SConfig cfg = i2s.defaultConfig(RX_MODE);
cfg.copyFrom(audioInfo);
cfg.pin_bck = I2S_BCK_PIN;
cfg.pin_ws = I2S_WS_PIN;
cfg.pin_data_rx = I2S_DATA_PIN;
cfg.is_master = true;
if (!i2s.begin(cfg)) {
Serial.println("ERROR: I2S init failed");
while (true);
}
Serial.println("Ready — angle | speech | rms");
}
// ─────────────────────────────────────────────────────────────────
static int32_t audioBuf[BLOCK_SAMPLES * CHANNELS];
void loop() {
// ── I2S read ─────────────────────────────────────────────────
size_t bytesRead = i2s.readBytes((uint8_t *)audioBuf, sizeof(audioBuf));
float rms = 0.0f;
if (bytesRead > 0) {
rms = compute_rms(audioBuf, bytesRead / sizeof(int32_t));
}
// ── DOA poll ─────────────────────────────────────────────────
unsigned long now = millis();
if (now - lastDoaMs >= DOA_INTERVAL_MS) {
lastDoaMs = now;
uint16_t doa = 0, speech = 0;
if (read_doa(doa, speech)) {
Serial.printf("%4u deg | %s | %.4f\n",
doa,
speech ? "SPEECH" : "silent",
rms
);
} else {
Serial.println("I2C read failed");
}
}
}
Soporte técnico y debate sobre el producto
Gracias por elegir nuestros productos. Estamos aquí para ofrecerte diferentes tipos de soporte y garantizar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para adaptarnos a diferentes preferencias y necesidades.

