|
ESP32 + TinyML Offline AI Voice Recognition: Building a Wake Word Detection System from Scratch

ESP32 + TinyML Offline AI Voice Recognition: Building a Wake Word Detection System from Scratch

Introduction

Voice assistants have become a standard feature in smart homes, but most solutions rely on cloud services — Xiao Ai, Tmall Genie, Alexa — they all need an internet connection to work. This not only raises privacy concerns but also introduces network latency and server dependency. Once the internet goes down, the entire system becomes useless.

What if you could perform speech recognition locally? No internet required, no cloud servers, no waiting for cloud responses — all computation happens on the device itself.

TinyML (Tiny Machine Learning) makes this possible. By running lightweight neural networks on microcontrollers like the ESP32-S3, we can achieve fully offline voice wake word detection with response latency under 200ms and power consumption in the milliwatt range. This means your device can operate independently without network access while protecting user privacy.

In this article, I’ll walk you through the entire process from scratch: training a speech recognition model with Edge Impulse, deploying it to an ESP32-S3, and implementing custom wake word detection like “Hello, Xiaozhi.” The whole process requires no deep learning background or expensive hardware. For under $12, you can build your own offline voice assistant.

What is TinyML? Why ESP32?

Core Concepts of TinyML

TinyML refers to running machine learning models on resource-constrained microcontrollers (MCUs). The core challenges come from three aspects:

  • Memory constraints: The ESP32-S3 has only 512KB of SRAM, while an image recognition model can easily be several MB. Speech recognition models are relatively smaller, but still require careful compression to fit.
  • Computing power constraints: A 240MHz dual-core CPU with no GPU acceleration. A single inference needs to complete millions of multiply-accumulate operations within tens of milliseconds, demanding significant algorithm optimization.
  • Power consumption constraints: Battery-powered devices need milliwatt-level power consumption. Traditional speech recognition solutions consume several watts, completely unsuitable for edge scenarios.

The solution is model compression techniques: Quantization compresses floating-point weights to 8-bit integers, reducing model size by 4x; Pruning removes unimportant connections to reduce computation; Knowledge Distillation uses a large model to guide small model training, dramatically reducing model size while minimizing accuracy loss. Through these techniques, a speech model that originally required 100MB can be compressed to under 200KB with accuracy loss controlled within 5%.

Why Choose ESP32-S3?

The ESP32-S3 is a chip released by Espressif in 2021, specifically designed for AIoT scenarios. Compared to the older ESP32, it has three key upgrades:

  1. Vector instruction acceleration: The Xtensa LX7 processor supports AI Vector Instructions, specifically optimized for matrix multiplication and convolution operations, delivering 2-3x faster inference than the older ESP32
  2. Larger memory space: Supports up to 8MB PSRAM, accommodating larger models and more audio buffers
  3. Rich peripheral interfaces: I2S interface for direct connection to digital microphones, USB OTG supporting UVC cameras, DVP parallel interface compatible with mainstream image sensors

For speech recognition tasks, the ESP32-S3’s I2S interface can directly connect to MEMS microphones with 16kHz sampling rate support (the standard for speech recognition). Combined with vector instruction acceleration, inference latency can be kept within 100-200ms.

Comparison with Other Solutions

SolutionCostLatencyPrivacyOffline Capability
ESP32-S3 + TinyML~$7<200msFully local✅ Fully offline
Raspberry Pi + Python~$42500ms-2sLocal/Cloud⚠️ Requires Linux
Cloud API (iFlytek/Baidu)Pay-per-use500ms-3s❌ Data uploaded❌ Internet required
Dedicated voice module (SU-03T)~$4<100msFully local✅ But commands are fixed

The ESP32-S3’s advantages are: low cost, customizable wake words, WiFi/Bluetooth connectivity for expansion, and a rich community ecosystem. Dedicated modules are cheaper and faster, but their commands are fixed and cannot be customized. The Raspberry Pi solution is flexible but has much higher power consumption and cost.

Hardware List

Before starting, prepare the following hardware:

  • ESP32-S3 development board: Recommended Seeed Studio XIAO ESP32S3 Sense or ESP32-S3-DevKitC-1. The XIAO series is only 21x51mm with built-in camera and microphone interfaces, suitable for wearable devices; DevKitC has more pins, better for prototyping and multi-sensor integration.
  • INMP441 I2S MEMS microphone module: Digital output, 61dB SNR, supports 16kHz/48kHz sampling rates, -26dBFS sensitivity. If using XIAO ESP32S3 Sense, the microphone is already integrated on the expansion board — no need to buy separately.
  • USB-C data cable: For flashing firmware and serial debugging. Make sure to buy a data cable, not a charge-only cable — many cheap cables can only charge and cannot transfer data.
  • Breadboard and jumper wires: For connecting the microphone to the development board. Short jumper wires (under 10cm) are recommended to reduce signal interference and noise.
  • LED or buzzer (optional): For indicating successful wake word detection, making it easy to visually confirm detection results during debugging.
  • 3.7V lithium battery (optional): For power consumption testing and mobile deployment scenarios, 1000mAh or above recommended.

Total cost is approximately $7-12, much cheaper than buying a commercial voice module, and fully customizable.

Hardware Wiring: I2S Microphone Connection

The INMP441 is a digital MEMS microphone that outputs audio data via the I2S (Inter-IC Sound) protocol. I2S is a serial bus designed specifically for audio, containing three signal lines:

  • BCLK (Bit Clock): Each clock cycle transmits one bit of data
  • LRCLK (Left-Right Clock): Left-right channel select clock, low level for left channel, high level for right channel
  • DOUT (Data Out): Data output line, 16-bit audio data is transmitted serially through this line

Wiring Diagram

INMP441 Microphone        ESP32-S3 Dev Board
─────────────────────────────────────
VDD                 3.3V
GND                 GND
BCLK                GPIO 26
LRCLK               GPIO 25
DOUT                GPIO 22
SD                  GND (chip enable, grounded = enabled)
L/R                 GND (left channel mode)

Important notes:

  • INMP441 operates at 1.7-3.3V — never connect to 5V or you may destroy the module
  • L/R pin connected to GND means left channel, connected to VDD means right channel — must match the channel setting in code
  • If using XIAO ESP32S3 Sense, the microphone is already integrated — skip this step
  • After wiring, use a multimeter to check there’s no short circuit between VDD and GND

Edge Impulse: Training the Speech Recognition Model

Edge Impulse is a machine learning platform designed specifically for embedded devices, providing end-to-end tools from data collection, signal processing, model training to firmware export. It’s free for individual developers, supports mainstream platforms like ESP32, Arduino, and STM32, and can directly export Arduino libraries — greatly simplifying the deployment process.

Step 1: Create a Project

  1. Visit edgeimpulse.com and register for a free account
  2. Click “Create new project” and name it esp32-voice-wakeup
  3. Select “Audio” as the data type
  4. Set sampling rate to 16000Hz (the standard sampling rate for speech recognition)
  5. Set sample length to 1000ms (1-second audio clips)

Step 2: Collect Audio Data

The speech recognition model needs two types of data:

  • Wake word: The keyword you want to recognize, like “Hello Xiaozhi” or “Start working”
  • Background noise: Ambient sounds, everyday conversations, music, TV sounds, etc.

Collect at least 30 minutes of samples for each type, covering different scenarios. Data volume directly determines model quality — this is the most important step.

Collection method:

  1. Select “Data acquisition” in the Edge Impulse console
  2. Connect ESP32-S3 as the collection device (select “From microphone” mode)
  3. Click “Start sampling” and say the wake word or record ambient sound
  4. Sample 1 second each time, repeat 50+ times

Data collection tips:

  • Collect at different distances (10cm, 50cm, 1m) to simulate real usage scenarios
  • Collect in different environments (quiet room, with background music, office with multiple people talking)
  • Have different people say the wake word to improve model generalization
  • Background noise should be as diverse as possible — keyboard clicks, footsteps, TV sounds, etc.
  • Recommended wake word to background noise ratio is 1:2 to prevent overfitting

Step 3: Design Feature Extraction

The key to speech recognition is extracting MFCC (Mel-frequency cepstral coefficients) features. MFCC simulates the human ear’s frequency perception — the human ear has better resolution for low-frequency sounds than high-frequency ones, and the Mel scale is designed based on this characteristic. MFCC is the most classic and mature feature extraction method in speech recognition.

Configure in Edge Impulse:

  1. Go to the “Create impulse” page
  2. Add “Audio” → “MFE” (Mel-frequency energies) signal processing block
  3. Parameter settings: Frame size 32ms, Frame stride 16ms, Number of coefficients 13
  4. Add a “Classification” learning block

The MFE processing block automatically splits 1-second audio into multiple frames, calculates the Mel-frequency energy spectrum for each frame, and generates a 2D feature matrix. This matrix is the input to the neural network.

Step 4: Train the Model

  1. Go to the “NN Classifier” page
  2. Set the network architecture:
    • Dense layer 1: 64 neurons, ReLU activation
    • Dropout 1: 0.25 (to prevent overfitting)
    • Dense layer 2: 32 neurons, ReLU activation
    • Dropout 2: 0.25
    • Dense layer 3: number of classes, Softmax activation
  3. Set training parameters:
    • Epochs: 50
    • Learning rate: 0.001
    • Batch size: 32
  4. Click “Train” and wait 5-10 minutes

Model evaluation: After training, check the confusion matrix on the “Model testing” page. A good model should achieve over 90% accuracy. If it’s below 85%, you need to increase training data, adjust the network architecture, or check data quality (labeling errors, excessive noise, etc.).

Step 5: Export the Model

  1. Go to the “Deployment” page
  2. Select “Arduino library”
  3. Check “Quantize model (INT8)” — quantizes the model to 8-bit integers, reducing model size by 4x and inference speed by 2-3x
  4. Click “Build” and download the .zip file

The exported library contains the trained model weights and inference code, and can be directly imported as a library in the Arduino IDE.

Arduino Code: Deploying to ESP32-S3

Step 1: Set Up the Development Environment

Complete the following configuration in Arduino IDE:

  1. Install ESP32 board support:

    • Open “File” → “Preferences” → “Additional Board Manager URLs”
    • Add https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
    • Search “esp32” in “Tools” → “Board” → “Board Manager” and install the latest version
  2. Import the Edge Impulse library:

    • “Sketch” → “Include Library” → “Add .ZIP Library”
    • Select the .zip file you just downloaded
  3. Configure board parameters:

    • Board: XIAO ESP32S3 Sense (or ESP32S3 Dev Module)
    • PSRAM: “OPI PSRAM” (if using XIAO Sense)
    • Flash Size: 8MB (or your board’s actual size)
    • Upload Speed: 921600

Step 2: Complete Code

// ESP32-S3 TinyML Offline Voice Wake Word Detection
// Requires Edge Impulse Arduino library

#include <esp32s3_ai_inference.h>
#include "esp32_voice_wakeup_inference.h"  // Library exported from Edge Impulse

// I2S microphone pin definitions (modify according to actual wiring)
#define I2S_BCLK    26
#define I2S_LRCLK   25
#define I2S_DOUT    22

// LED indicator pin
#define LED_PIN     2

// Confidence threshold (0.0-1.0), only trigger wake above this value
#define CONFIDENCE_THRESHOLD  0.80

// Audio buffer
static int16_t audio_buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];

// Initialize I2S microphone
void init_microphone() {
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
        .sample_rate = 16000,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 4,
        .dma_buf_len = 512,
    };

    i2s_pin_config_t pin_config = {
        .bck_io_num   = I2S_BCLK,
        .ws_io_num    = I2S_LRCLK,
        .data_out_num = I2S_PIN_NO_CHANGE,
        .data_in_num  = I2S_DOUT,
    };

    i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM_0, &pin_config);
    i2s_zero_dma_buffer(I2S_NUM_0);
    Serial.println("✓ I2S microphone initialized");
}

// Read one frame of audio data from the microphone
void read_audio() {
    size_t bytes_read = 0;
    i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer),
             &bytes_read, portMAX_DELAY);
}

void setup() {
    Serial.begin(115200);
    while (!Serial) { delay(10); }
    Serial.println("═══════════════════════════════════");
    Serial.println("  ESP32-S3 TinyML Voice Wake System");
    Serial.println("═══════════════════════════════════");

    // Initialize LED indicator
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, LOW);

    // Initialize microphone
    init_microphone();

    // Print model information
    Serial.printf("Model input size: %d samples\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
    Serial.printf("Sampling rate: %d Hz\n", EI_CLASSIFIER_FREQUENCY);
    Serial.printf("Number of classes: %d\n", EI_CLASSIFIER_LABEL_COUNT);
    for (int i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
        Serial.printf("  Class %d: %s\n", i, ei_classifier_get_label(i));
    }
    Serial.println("═══════════════════════════════════");
    Serial.println("Listening for wake word...");
}

void loop() {
    // 1. Read audio data from the microphone
    read_audio();

    // 2. Run inference (feature extraction + classification)
    ei_impulse_result_t result = {0};
    EI_IMPULSE_ERROR_t res = run_classifier(audio_buffer, &result, false);

    if (res != EI_IMPULSE_OK) {
        Serial.printf("Inference failed, error code: %d\n", res);
        return;
    }

    // 3. Find the class with highest confidence
    int max_index = 0;
    float max_value = 0;
    for (int i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
        if (result.classification[i].value > max_value) {
            max_value = result.classification[i].value;
            max_index = i;
        }
    }

    // 4. If wake word confidence exceeds threshold, trigger action
    if (max_value > CONFIDENCE_THRESHOLD
        && strcmp(result.classification[max_index].label, "wakeup_word") == 0) {

        uint32_t total_ms = result.timing.dsp_us / 1000
                          + result.timing.classification_us / 1000;

        Serial.printf("\n🎤 Wake word detected!\n");
        Serial.printf("   Confidence: %.1f%%\n", max_value * 100);
        Serial.printf("   Inference latency: %d ms\n", total_ms);
        Serial.printf("   Feature extraction: %d ms\n", result.timing.dsp_us / 1000);
        Serial.printf("   Model inference: %d ms\n\n", result.timing.classification_us / 1000);

        // Flash LED for 1 second as visual feedback
        digitalWrite(LED_PIN, HIGH);
        delay(500);
        digitalWrite(LED_PIN, LOW);

        // Add follow-up actions here, for example:
        // - Send commands to Home Assistant via MQTT
        // - Start subsequent voice command recognition
        // - Control a relay to turn on lights
        // - Play a notification sound (via I2S DAC)
    }

    delay(50);  // 50ms interval, balancing response speed and CPU usage
}

Step 3: Flash and Test

  1. Connect the ESP32-S3 to your computer via USB-C cable
  2. Select the correct board model and serial port in Arduino IDE
  3. Click “Upload” to flash the firmware (you may need to hold the BOOT button on first flash)
  4. Open the serial monitor (baud rate 115200)
  5. Say the wake word into the microphone and observe the serial output

Debugging tips:

  • If the wake word isn’t detected, first check that the microphone wiring is correct — use an oscilloscope or logic analyzer to verify I2S signals
  • If the false trigger rate is high (triggering without speaking), raise the confidence threshold (from 0.80 to 0.90)
  • If the miss rate is high (not detecting when spoken), lower the threshold (from 0.80 to 0.70) and check if training data is sufficient
  • Check inference latency — it should normally be in the 100-200ms range

Power Optimization and Performance Benchmarks

Inference Performance Benchmarks

Tested on ESP32-S3 @ 240MHz with PSRAM enabled:

MetricValue
Model size (quantized)180 KB
Total inference latency120-180 ms
Feature extraction time30-50 ms
Classification inference time90-130 ms
SRAM usage45 KB
Test accuracy92-95%

Power Optimization Strategies

For battery-powered IoT devices, power consumption is a critical metric. Here are several practical optimization methods:

1. Reduce CPU Frequency

Inference doesn’t need 240MHz — 160MHz is sufficient:

// Reduce CPU frequency to 160MHz, reducing power consumption by ~30%
setCpuFrequencyMhz(160);

2. Intermittent Listening Mode

When continuous listening isn’t needed, let the MCU enter light sleep between detections:

// Listen every 5 seconds, enter light sleep the rest of the time
esp_sleep_enable_timer_wakeup(5 * 1000000);  // 5 seconds
esp_light_sleep_start();

3. Disable Unnecessary Wireless Modules

If internet connectivity isn’t needed, completely disable WiFi and Bluetooth:

WiFi.mode(WIFI_OFF);
btStop();

Power Consumption Test Results:

Operating ModeAverage Power2000mAh Battery Life
Continuous listening (240MHz)120 mA~16 hours
Continuous listening (160MHz)85 mA~23 hours
Intermittent listening (5s interval)15 mA~130 hours
Deep sleep + timer wake0.5 mA~4000 hours

Using intermittent listening mode, a 2000mAh lithium battery can support over 5 days of operation. Combined with deep sleep, battery life can be extended to several months.

Extended Applications

This offline voice wake system can be extended to many practical scenarios:

Smart home control: After wake-up, send commands via WiFi/MQTT to Home Assistant to control lights, air conditioning, curtains, and other devices. It can also be extended to multi-level command recognition — “turn on lights,” “turn off lights,” “raise temperature.”

Security monitoring: Detect abnormal sounds (glass breaking, alarm sirens, baby crying), combined with ESP32’s camera for sound source localization and recording triggers.

Industrial predictive maintenance: Collect equipment operating sounds and train anomaly detection models. Automatically alert when machines produce abnormal sounds — more intuitive than traditional vibration sensor solutions.

Accessibility assistance: Provide voice control interfaces for people with limited mobility. Offline operation ensures privacy and is unaffected by network conditions.

FAQ

Q: What if the model accuracy isn’t high enough? A: Increasing training data is the most effective approach. Try collecting data in different environments, at different distances, and from different speakers. You can also use Edge Impulse’s data augmentation features (adding noise, time shifting, speed variation) to expand your dataset.

Q: Can it recognize multiple wake words or command words? A: Yes. Create a separate class for each keyword in Edge Impulse and train a multi-class classification model. However, it’s recommended to keep command words to no more than 5, otherwise accuracy will decrease. If you need more commands, consider a two-stage architecture: wake word + command words.

Q: How can I improve inference speed? A: Enable PSRAM (external memory) to free up SRAM, reduce model complexity (fewer neurons), and use INT8 quantization. The ESP32-S3’s vector instructions are automatically enabled in ESP-IDF 5.x.

Q: Can I use Chinese wake words? A: Absolutely. Edge Impulse supports speech data in any language — just collect enough Chinese training samples. For Chinese wake words, choose short phrases with 3-4 syllables, like “你好小智” (Hello Xiaozhi) or “开始工作” (Start working).

Summary

Through this article, we’ve built a fully offline ESP32-S3 voice wake system. Key takeaways:

  1. TinyML makes edge AI possible: Running neural networks on microcontrollers costing a few dollars, with no cloud dependency
  2. Edge Impulse simplifies the development workflow: From data collection to model deployment — fully visual operations, no need to write training code by hand
  3. ESP32-S3 is an ideal TinyML platform: Vector instruction acceleration, low power consumption, rich peripheral interfaces, active community ecosystem
  4. Offline speech recognition has broad applications: Smart home, security monitoring, industrial maintenance, accessibility assistance

Next steps you can try:

  • Integrate more sensors (temperature/humidity, accelerometer, barometer) to create multi-modal sensing nodes
  • Add voice command recognition (not just wake words, but also commands like “turn on lights” and “turn off lights”)
  • Use ESP-NOW to build a multi-node voice network for whole-house coverage
  • Combine with our previous ESP32-S3 image recognition article for dual audio-visual AI modality

Feel free to discuss in the comments or join our Telegram group to share hardware development experiences. See you next time!