Embedded Development 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:
- 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
- Larger memory space: Supports up to 8MB PSRAM, accommodating larger models and more audio buffers
- 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
| Solution | Cost | Latency | Privacy | Offline Capability |
|---|---|---|---|---|
| ESP32-S3 + TinyML | ~$7 | <200ms | Fully local | ✅ Fully offline |
| Raspberry Pi + Python | ~$42 | 500ms-2s | Local/Cloud | ⚠️ Requires Linux |
| Cloud API (iFlytek/Baidu) | Pay-per-use | 500ms-3s | ❌ Data uploaded | ❌ Internet required |
| Dedicated voice module (SU-03T) | ~$4 | <100ms | Fully 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
- Visit edgeimpulse.com and register for a free account
- Click “Create new project” and name it
esp32-voice-wakeup - Select “Audio” as the data type
- Set sampling rate to 16000Hz (the standard sampling rate for speech recognition)
- 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:
- Select “Data acquisition” in the Edge Impulse console
- Connect ESP32-S3 as the collection device (select “From microphone” mode)
- Click “Start sampling” and say the wake word or record ambient sound
- 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:
- Go to the “Create impulse” page
- Add “Audio” → “MFE” (Mel-frequency energies) signal processing block
- Parameter settings: Frame size 32ms, Frame stride 16ms, Number of coefficients 13
- 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
- Go to the “NN Classifier” page
- 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
- Set training parameters:
- Epochs: 50
- Learning rate: 0.001
- Batch size: 32
- 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
- Go to the “Deployment” page
- Select “Arduino library”
- Check “Quantize model (INT8)” — quantizes the model to 8-bit integers, reducing model size by 4x and inference speed by 2-3x
- Click “Build” and download the
.zipfile
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:
-
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
-
Import the Edge Impulse library:
- “Sketch” → “Include Library” → “Add .ZIP Library”
- Select the
.zipfile you just downloaded
-
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
- Connect the ESP32-S3 to your computer via USB-C cable
- Select the correct board model and serial port in Arduino IDE
- Click “Upload” to flash the firmware (you may need to hold the BOOT button on first flash)
- Open the serial monitor (baud rate 115200)
- 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:
| Metric | Value |
|---|---|
| Model size (quantized) | 180 KB |
| Total inference latency | 120-180 ms |
| Feature extraction time | 30-50 ms |
| Classification inference time | 90-130 ms |
| SRAM usage | 45 KB |
| Test accuracy | 92-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 Mode | Average Power | 2000mAh 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 wake | 0.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:
- TinyML makes edge AI possible: Running neural networks on microcontrollers costing a few dollars, with no cloud dependency
- Edge Impulse simplifies the development workflow: From data collection to model deployment — fully visual operations, no need to write training code by hand
- ESP32-S3 is an ideal TinyML platform: Vector instruction acceleration, low power consumption, rich peripheral interfaces, active community ecosystem
- 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!