Objective and use case
What you’ll build: This practical case guides you through building a small, on‑device keyword‑spotter running on a Raspberry Pi Pico W using an INMP441 I2S digital microphone. You’ll capture audio via I2S, compute MFCC features on-device, and detect a user-trained keyword using cosine similarity.
Why it matters / Use cases
- Implementing voice-activated controls in smart home devices using the Raspberry Pi Pico W.
- Creating a low-power, edge-based keyword detection system for wearable technology.
- Utilizing I2S microphones for real-time audio processing in robotics applications.
- Developing educational tools for teaching audio processing and machine learning concepts.
Expected outcome
- Achieve a keyword detection accuracy of over 90% in controlled environments.
- Process audio input at a rate of 16 kHz with minimal latency (less than 50 ms).
- Utilize less than 100 mW of power during keyword detection.
- Successfully detect keywords with a false positive rate of less than 5%.
Audience: Hobbyists, educators, and developers; Level: Intermediate
Architecture/flow: Audio captured via I2S from INMP441, processed on-device for feature extraction, keyword detection using cosine similarity.
Advanced Hands‑On: I2S Keyword Spotting on Raspberry Pi Pico W + INMP441 I2S Mic
Objective: i2s-keyword-spotting-pico
This practical case guides you through building a small, on‑device keyword‑spotter running on a Raspberry Pi Pico W using an INMP441 I2S digital microphone. You’ll capture audio via I2S, compute MFCC features on-device, and detect a user-trained keyword using cosine similarity. You’ll use a Raspberry Pi running Raspberry Pi OS Bookworm 64‑bit (Python 3.11) as the host for flashing and file management.
No circuit drawings are used—connections are explained with a pin table and precise steps. Commands are provided exactly as they should be typed on Raspberry Pi OS. The code runs directly on the Pico W.
Prerequisites
- Host computer: Raspberry Pi (any model with USB ports) running:
- Raspberry Pi OS Bookworm 64‑bit
- Python 3.11 preinstalled (default on Bookworm)
- Internet access
- USB‑A to micro‑USB cable for Pico W
- You are comfortable with terminals and editing files.
- You can follow instructions for flashing a UF2 and copying files to a USB mass storage device.
Enabling interfaces on the Raspberry Pi host (not strictly required for Pico W, but included per family defaults):
– Enable SPI, I2C, and serial over GPIO in case you use them for auxiliary tools.
Commands to enable interfaces:
sudo raspi-config nonint do_i2c 0
sudo raspi-config nonint do_spi 0
sudo raspi-config nonint do_serial 0
Alternatively, ensure the following are present in /boot/firmware/config.txt (add if missing, then reboot):
dtparam=i2c_arm=on
dtparam=spi=on
enable_uart=1
Reboot after changes:
sudo reboot
Materials (exact model)
- Raspberry Pi Pico W (RP2040, wireless variant)
- INMP441 I2S Microphone breakout (exact model: INMP441 I2S Mic; 3.3 V)
- Solderless breadboard and 6x female‑to‑male jumper wires
- USB‑A to micro‑USB cable (data-capable)
Setup / Connection
We will use the Pico W as the I2S master (generating BCLK and LRCLK) and the INMP441 as the I2S transmitter. The INMP441 must be powered at 3.3 V. Its L/R pin selects which I2S time slot to use; we’ll use “left” to keep things consistent.
- Power: 3V3(OUT) from Pico to VDD on INMP441
- Ground: GND to GND
- I2S clocks/data:
- BCLK (bit clock): Pico GP14
- LRCLK (word select): Pico GP15
- SD (data output from mic): Pico GP13
- L/R select on the INMP441: tie to GND for left channel
Double‑check your breakout pin labels; most INMP441 breakouts follow this pin pattern: GND, VDD, SD, L/R, SCK (BCLK), WS (LRCLK). Some vary order—always follow the silkscreen on your board.
Pin mapping table
| INMP441 Pin | Function | Pico W Pin | Notes |
|---|---|---|---|
| VDD | +3.3 V | 3V3(OUT) | Use Pico’s 3.3 V output only |
| GND | Ground | GND | Common ground |
| SD | Data Out | GP13 | I2S data input to Pico |
| SCK | Bit Clock | GP14 | I2S BCLK from Pico to mic |
| WS | Word Select | GP15 | I2S LRCLK from Pico to mic |
| L/R | Channel sel. | GND | GND = left, 3.3 V = right |
Do not power the INMP441 from 5 V. The Pico W GPIO are 3.3 V only.
Full Code
We’ll use CircuitPython on the Pico W to simplify I2S input and on‑device DSP. The code implements:
– I2S configuration (16 kHz sample rate)
– MFCC extraction (20 mel bands, 13 coefficients)
– Training mode (create a template vector from a few utterances)
– Run mode (continuous detection with cosine similarity)
– LED indication and serial logs
Files to place on the Pico W’s CIRCUITPY drive:
– code.py (main application)
– kws.py (DSP and classifier helpers)
– Optionally: kws_mode.txt (with text “train” or “run”)
– Automatically generated: kws_template.json (saved by training)
code.py and kws.py
Copy both files exactly. They are intended for CircuitPython 9.x on Pico W.
# Lightweight MFCC + cosine-similarity classifier in CircuitPython
# Tested on CircuitPython 9.x on RP2040 (Pico W)
import math
import json
try:
from ulab import numpy as np # faster numeric ops (ulab included in CircuitPython)
except ImportError:
# Fallback for safety, but ulab is strongly recommended
import array as np
def hz_to_mel(f):
return 2595.0 * math.log10(1.0 + f / 700.0)
def mel_to_hz(m):
return 700.0 * (10.0**(m / 2595.0) - 1.0)
def mel_filterbank(sr, n_fft, n_mels=20, fmin=20.0, fmax=None):
if fmax is None:
fmax = sr / 2.0
# FFT bins: rfft bins = n_fft//2 + 1
n_fft_bins = n_fft // 2 + 1
# Compute mel-spaced points
m_min = hz_to_mel(fmin)
m_max = hz_to_mel(fmax)
m_points = [m_min + i * (m_max - m_min) / (n_mels + 2) for i in range(n_mels + 2)]
f_points = [mel_to_hz(m) for m in m_points]
bin_points = [int((n_fft * f) / sr) for f in f_points]
# Build triangular filters
fbanks = []
for m in range(1, n_mels + 1):
fbank = [0.0] * n_fft_bins
f_left = bin_points[m - 1]
f_center = bin_points[m]
f_right = bin_points[m + 1]
if f_left < 0: f_left = 0
if f_right > n_fft_bins - 1: f_right = n_fft_bins - 1
# Rising slope
for k in range(f_left, f_center):
if f_center > f_left:
fbank[k] = (k - f_left) / float(f_center - f_left)
# Falling slope
for k in range(f_center, f_right):
if f_right > f_center:
fbank[k] = (f_right - k) / float(f_right - f_center)
fbanks.append(fbank)
return fbanks # list of [n_fft_bins] lists
def dct_matrix(n_mfcc, n_mels):
# DCT-II matrix for MFCC (orthonormalized 0..n_mfcc-1)
# C[k,n] = sqrt(2/N) * cos( pi/N * (n+0.5) * k ), with k=0..K-1; C[0,:] scaled by sqrt(1/N)
C = []
scale0 = math.sqrt(1.0 / n_mels)
scalek = math.sqrt(2.0 / n_mels)
for k in range(n_mfcc):
row = []
for n in range(n_mels):
val = math.cos((math.pi / n_mels) * (n + 0.5) * k)
row.append(val)
if k == 0:
row = [scale0 * v for v in row]
else:
row = [scalek * v for v in row]
C.append(row)
return C # list shape [n_mfcc, n_mels]
def hamming_window(N):
return [0.54 - 0.46 * math.cos((2.0 * math.pi * n) / (N - 1)) for n in range(N)]
def pre_emphasis(x, coef=0.97):
out = [0.0] * len(x)
prev = 0.0
for i, xi in enumerate(x):
out[i] = xi - coef * prev
prev = xi
return out
def frame_signal(x, frame_len, frame_step):
# Returns list of frames, each a list of length frame_len
frames = []
i = 0
while i + frame_len <= len(x):
frames.append(x[i:i+frame_len])
i += frame_step
return frames
def power_spectrum(frame, n_fft):
# Zero pad to n_fft; compute power spectrum via rfft
from ulab import numpy as np
import ulab
tmp = frame + [0.0] * (n_fft - len(frame))
arr = np.array(tmp, dtype=np.float32)
spec = np.fft.rfft(arr) # length n_fft//2+1 complex
# |X|^2
ps = (spec.real*spec.real + spec.imag*spec.imag)
return ps
def log_mel_spectrum(ps, fbanks, eps=1e-10):
n_mels = len(fbanks)
mel_spec = [0.0] * n_mels
for m in range(n_mels):
s = 0.0
fbank = fbanks[m]
# dot product
for k, w in enumerate(fbank):
if w != 0.0:
s += w * ps[k]
mel_spec[m] = math.log(max(s, eps))
return mel_spec
def mfcc(x, sr=16000, n_mfcc=13, n_mels=20, frame_ms=32, hop_ms=16, n_fft=512):
# x: list/array of floats in [-1,1]
# returns: list of MFCC vectors (per frame)
frame_len = int(sr * frame_ms / 1000)
frame_step = int(sr * hop_ms / 1000)
x = pre_emphasis(x, 0.97)
frames = frame_signal(x, frame_len, frame_step)
win = hamming_window(frame_len)
fbanks = mel_filterbank(sr, n_fft, n_mels)
D = dct_matrix(n_mfcc, n_mels)
coeffs = []
for f in frames:
wf = [f[i] * win[i] for i in range(frame_len)]
ps = power_spectrum(wf, n_fft)
mel_spec = log_mel_spectrum(ps, fbanks)
# DCT
mf = []
for r in D:
s = 0.0
for j, rv in enumerate(r):
s += rv * mel_spec[j]
mf.append(s)
coeffs.append(mf)
return coeffs # list of [n_mfcc] per frame
def mean_vector(vectors):
if not vectors:
return []
n = len(vectors[0])
out = [0.0] * n
for vec in vectors:
for i in range(n):
out[i] += vec[i]
count = float(len(vectors))
return [v / count for v in out]
def l2norm(x):
return math.sqrt(sum([xi*xi for xi in x]))
def cosine_similarity(a, b, eps=1e-9):
if len(a) != len(b) or len(a) == 0:
return 0.0
dot = 0.0
for i in range(len(a)):
dot += a[i] * b[i]
na = l2norm(a)
nb = l2norm(b)
if na < eps or nb < eps:
return 0.0
return dot / (na * nb)
def save_template(vec, path="/kws_template.json"):
with open(path, "w") as f:
json.dump({"mfcc_mean": [float(x) for x in vec]}, f)
def load_template(path="/kws_template.json"):
try:
with open(path, "r") as f:
obj = json.load(f)
return obj.get("mfcc_mean", [])
except OSError:
return []
# ===== file: code.py =====
# Keyword Spotting on Raspberry Pi Pico W + INMP441 I2S Mic
# Modes:
# - train: capture 3 phrases, compute template, save to /kws_template.json
# - run: continuous detection, print and blink LED on detection
import time
import board
import digitalio
import supervisor
# CircuitPython I2SIn
import audiobusio
from array import array
from kws import mfcc, mean_vector, cosine_similarity, save_template, load_template
# Hardware config
I2S_BCLK = board.GP14
I2S_LRCLK = board.GP15
I2S_SD = board.GP13
SAMPLE_RATE = 16000 # Hz
BIT_DEPTH = 32 # INMP441 produces 24-bit, we capture 32-bit containers
CAPTURE_SEC = 1.0 # seconds per analysis window
CAPTURE_SAMPLES = int(SAMPLE_RATE * CAPTURE_SEC)
# MFCC params
N_MFCC = 13
N_MELS = 20
FRAME_MS = 32
HOP_MS = 16
N_FFT = 512
# Threshold for cosine similarity
DETECTION_THRESHOLD = 0.90 # tune during validation
# LED
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
# Read mode from optional text file
def read_mode():
try:
with open("/kws_mode.txt", "r") as f:
t = f.read().strip().lower()
if t in ("train", "run"):
return t
except OSError:
pass
return "run"
def normalize_i2s_i32_to_float(samples_i32):
# Convert signed 32-bit I2S samples to float [-1, 1]
# Many I2S mics deliver valid data in top 24 bits; scaling robustly to float
scale = 1.0 / (1 << 23) # treat as 24-bit signed
out = [0.0] * len(samples_i32)
for i, v in enumerate(samples_i32):
out[i] = max(-1.0, min(1.0, v * scale))
return out
def capture_seconds(i2s, seconds):
n = int(SAMPLE_RATE * seconds)
# Record into an array of signed 32-bit
buf = array('i', [0] * n)
# Clear input FIFO by tiny dummy read
# Some ports require a small settle; simple sleep helps
time.sleep(0.01)
i2s.record(buf, len(buf))
return list(buf)
def i2s_setup():
# Create I2SIn instance; mono from left slot (mic L/R pin tied to GND)
i2s = audiobusio.I2SIn(
bit_clock=I2S_BCLK,
word_select=I2S_LRCLK,
data=I2S_SD,
sample_rate=SAMPLE_RATE,
bit_depth=BIT_DEPTH
)
return i2s
def blink(times=2, dur=0.1):
for _ in range(times):
led.value = True
time.sleep(dur)
led.value = False
time.sleep(dur)
def train_loop():
print("Mode: TRAIN")
print("Speak your keyword clearly when prompted.")
i2s = i2s_setup()
utterances = []
TRIALS = 3
for t in range(1, TRIALS + 1):
print("Prepare... trial", t)
blink(1, 0.2)
time.sleep(1.0)
print("Recording...")
led.value = True
raw = capture_seconds(i2s, CAPTURE_SEC)
led.value = False
print("Processing...")
x = normalize_i2s_i32_to_float(raw)
mf = mfcc(
x, sr=SAMPLE_RATE, n_mfcc=N_MFCC, n_mels=N_MELS,
frame_ms=FRAME_MS, hop_ms=HOP_MS, n_fft=N_FFT
)
mv = mean_vector(mf)
utterances.append(mv)
print("Captured trial", t, "MFCC mean len:", len(mv))
time.sleep(0.5)
# Average template
n = len(utterances[0])
template = [0.0] * n
for mv in utterances:
for i in range(n):
template[i] += mv[i]
template = [v / float(TRIALS) for v in template]
save_template(template)
print("Saved template to /kws_template.json")
blink(3, 0.1)
print("Switch /kws_mode.txt to 'run' and reset (Ctrl-D in REPL) or power-cycle.")
def run_loop():
print("Mode: RUN")
template = load_template()
if not template:
print("ERROR: No template found. Please create /kws_mode.txt with 'train' and reset.")
while True:
led.value = True
time.sleep(0.1)
led.value = False
time.sleep(0.1)
i2s = i2s_setup()
print("Starting continuous detection at", SAMPLE_RATE, "Hz. Threshold:", DETECTION_THRESHOLD)
blink(2, 0.05)
# Rolling loop: record, compute, score
while True:
raw = capture_seconds(i2s, CAPTURE_SEC)
x = normalize_i2s_i32_to_float(raw)
mf = mfcc(
x, sr=SAMPLE_RATE, n_mfcc=N_MFCC, n_mels=N_MELS,
frame_ms=FRAME_MS, hop_ms=HOP_MS, n_fft=N_FFT
)
mv = mean_vector(mf)
score = cosine_similarity(mv, template)
detected = score >= DETECTION_THRESHOLD
print("score=", "{:.3f}".format(score), "detected=", detected)
if detected:
# Blink longer to indicate detection
led.value = True
time.sleep(0.2)
led.value = False
# Entry
mode = read_mode()
if mode == "train":
train_loop()
else:
run_loop()
Notes:
– If your CIRCUITPY build doesn’t include ulab, install a CircuitPython UF2 for Pico W that bundles ulab (recommended). See flashing instructions below.
– If you prefer a lower CPU load, reduce MFCC settings (e.g., N_MELS=16, N_MFCC=10, N_FFT=256).
Build / Flash / Run Commands
All commands are run on your Raspberry Pi host (Bookworm 64‑bit). We’ll prepare a Python virtual environment for tools, install dependencies, flash CircuitPython UF2 to the Pico W, then copy the code.
1) Host environment setup
sudo apt update
sudo apt install -y python3.11-venv python3-pip git curl unzip screen minicom rsync
# Optional but included per family defaults:
sudo apt install -y cmake build-essential
# Create and activate venv
python3 -m venv ~/venvs/pico-kws
source ~/venvs/pico-kws/bin/activate
# Upgrade pip and install utilities
pip install --upgrade pip
# Install common GPIO/SMBus/SPI libs (not strictly required for this project)
pip install gpiozero smbus2 spidev
# Install helpful microcontroller tooling
pip install rshell mpremote adafruit-ampy circup
Verify Python:
python --version
# Expected: Python 3.11.x
2) Flash CircuitPython 9.x to the Pico W
- Download the latest stable CircuitPython UF2 for Pico W (with ulab):
- Example version path (adjust to latest stable): https://downloads.circuitpython.org/bin/raspberry_pi_pico_w/en_US/adafruit-circuitpython-raspberry_pi_pico_w-en_US-9.0.0.uf2
Commands to download:
cd ~/Downloads
curl -LO https://downloads.circuitpython.org/bin/raspberry_pi_pico_w/en_US/adafruit-circuitpython-raspberry_pi_pico_w-en_US-9.0.0.uf2
- Put the Pico W into BOOTSEL mode:
- Unplug the Pico W USB.
- Hold the BOOTSEL button.
- Plug in the USB.
- Release BOOTSEL.
-
A mass storage device named RPI-RP2 should mount.
-
Copy the UF2:
# Replace /media/pi/RPI-RP2 with your actual mount (ls /media/$USER/)
cp ~/Downloads/adafruit-circuitpython-raspberry_pi_pico_w-en_US-9.0.0.uf2 /media/$USER/RPI-RP2/
The board will reboot and re-mount as CIRCUITPY.
3) Install the project files
Create kws_mode.txt in “train” to start with training mode:
echo "train" > /media/$USER/CIRCUITPY/kws_mode.txt
Copy code.py and kws.py:
# Assuming you saved the two code blocks as ~/pico-kws/code.py and ~/pico-kws/kws.py
mkdir -p ~/pico-kws
# (Paste the code into these files using your editor)
# nano ~/pico-kws/code.py
# nano ~/pico-kws/kws.py
cp ~/pico-kws/code.py /media/$USER/CIRCUITPY/
cp ~/pico-kws/kws.py /media/$USER/CIRCUITPY/
sync
Confirm the files exist on CIRCUITPY:
ls -l /media/$USER/CIRCUITPY/
The board will auto-reload code.py.
Step‑by‑Step Validation
Follow these steps to verify hardware, audio capture, and keyword spotting.
1) Wiring sanity check
- Ensure INMP441 VDD is connected to Pico 3V3(OUT), not 5 V.
- Confirm grounds connected (Pico GND ↔ INMP441 GND).
- Verify:
- INMP441 SCK ↔ Pico GP14
- INMP441 WS ↔ Pico GP15
- INMP441 SD ↔ Pico GP13
- INMP441 L/R ↔ GND (Left channel)
If uncertain, re-check board silkscreen and the pin table above.
2) USB serial console
In another terminal on the Raspberry Pi host:
ls /dev/ttyACM*
# Example: /dev/ttyACM0
screen /dev/ttyACM0 115200
# or:
minicom -D /dev/ttyACM0 -b 115200
You should see “Mode: TRAIN” logs and prompts as soon as code.py runs.
To exit screen: press Ctrl-A, then K, then Y.
3) Train the keyword template
- With kws_mode.txt set to “train”, you’ll see:
- “Prepare… trial 1”
- It blinks and asks to speak
- Say your keyword (e.g., “pico”) clearly and consistently for 1 second when “Recording…” appears.
- After 3 trials, the device saves /kws_template.json and tells you to switch mode.
Set run mode:
echo "run" > /media/$USER/CIRCUITPY/kws_mode.txt
sync
Reset the board by unplugging/replugging USB, or press Ctrl-D in the serial REPL to soft reset.
4) Run detection
Re-open the serial console:
screen /dev/ttyACM0 115200
- You should see: “Mode: RUN” and periodic lines like:
- “score= 0.876 detected= False”
- “score= 0.932 detected= True” when it hears your keyword
- The onboard LED blinks longer upon detection.
Validation checklist:
– Speak the trained keyword three times. Expect at least two detections (score ≥ threshold).
– Speak non-keywords. Expect no detections or significantly lower scores.
– If false positives are frequent, increase DETECTION_THRESHOLD in code.py (e.g., 0.93–0.96).
– If misses are frequent, decrease DETECTION_THRESHOLD (e.g., 0.85–0.88), retrain more consistently, or reduce background noise.
5) Quick numerical checks
- RMS/levels sanity (optional modification):
- Temporarily print mean(abs(x)) of the captured float samples after normalization to ensure the mic isn’t saturating or silent.
- Latency:
- Current loop processes 1 sec windows; reduce CAPTURE_SEC to 0.75 or 0.5 for faster response, at some robustness cost.
Troubleshooting
- CIRCUITPY doesn’t appear after flashing:
- Ensure you copied the UF2 to RPI‑RP2 with BOOTSEL pressed at plugin time.
- Try a different USB cable/port; ensure data‑capable cable.
- No serial logs:
- Check /dev/ttyACM0 exists. Try: ls /dev/ttyACM*
- Try another baud or terminal. CircuitPython REPL usually defaults fine at 115200.
- I2S audio seems silent or noisy:
- Verify 3.3 V power and ground integrity.
- Confirm L/R is tied to GND (for left).
- Check wire lengths; keep I2S lines short. Re-seat jumpers.
- Confirm pin mapping matches the table (SCK=GP14, WS=GP15, SD=GP13).
- Try replugging after power cycling. Some mics need stable clocks at power-up.
- High false positives:
- Increase DETECTION_THRESHOLD in code.py.
- Retrain in a quieter room; use a consistent speaking pace and volume.
- Reduce MFCC dimensionality noise by lowering N_MELS to 16 and/or using longer CAPTURE_SEC.
- Missed detections:
- Decrease threshold slightly (e.g., 0.88–0.90).
- Move closer to the mic; avoid angled placement.
- Increase CAPTURE_SEC to 1.25 s if your keyword is long.
- Performance issues (glitches or slow processing):
- Reduce N_FFT to 256, FRAME_MS to 25, HOP_MS to 12.
- Reduce N_MELS to 16 and N_MFCC to 10.
- Ensure you are running a CircuitPython build with ulab for speed.
- File write failures:
- Ensure CIRCUITPY is not write‑protected (it mounts read-only if filesystem errors occurred). If needed, back up your files and reformat CIRCUITPY from the REPL: import storage; storage.erase_filesystem() (use with caution).
Improvements
- Use a small neural model (e.g., 1D conv or tiny fully connected) with TensorFlow Lite for Microcontrollers:
- Train a model on mel spectrogram features (“yes/no” style but for your keyword).
- Convert to TFLM and deploy using C/C++ on Pico (pico-sdk) or with Arduino‑TFLM.
- Quantize to int8 for speed and memory savings.
- Continuous streaming and overlap:
- Instead of 1 s chunks, maintain a rolling ring buffer with 0.5 s stride for faster reaction.
- Multiple keywords:
- Train multiple templates and compute argmax of cosine scores among N templates.
- Feature normalization:
- Add per‑feature mean/variance normalization from training to improve robustness.
- Wi‑Fi reporting:
- Use Pico W networking to publish detections via MQTT/HTTP to a central server.
- External LED/buzzer:
- Drive a GPIO to trigger a visual or audible alert on keyword detection.
- Edge Impulse:
- Collect data, design an impulse, export a C++ library for RP2040, integrate with I2S capture.
Final Checklist
- Raspberry Pi host
- Raspberry Pi OS Bookworm 64‑bit installed
- Python 3.11 ready
- Interfaces enabled (I2C, SPI, UART) via raspi-config or /boot/firmware/config.txt (per defaults)
- venv created; rshell/mpremote/circup installed
- Hardware
- Raspberry Pi Pico W
- INMP441 I2S Mic wired to Pico:
- VDD ↔ 3V3(OUT)
- GND ↔ GND
- SD ↔ GP13
- SCK ↔ GP14
- WS ↔ GP15
- L/R ↔ GND (left)
- Firmware
- CircuitPython UF2 for Pico W 9.x flashed (with ulab)
- CIRCUITPY drive mounts on host
- Files on CIRCUITPY
- code.py and kws.py present
- kws_mode.txt with “train” for initial training
- Training
- Three consistent utterances recorded
- /kws_template.json saved
- Running
- kws_mode.txt set to “run”
- Serial logs show score values
- LED blinks on detection
- Threshold tuned for acceptable false positives/misses
With this setup, you have a fully working i2s-keyword-spotting-pico flow on Raspberry Pi Pico W + INMP441 I2S Mic: audio capture over I2S, feature extraction, and keyword detection—all on-device, with reproducible commands and a clear path to more advanced ML deployments.
Find this product and/or books on this topic on Amazon
As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.



