Roni Bandini β Buenos Aires, Argentina β September 2023
Photon 2 Voice Command is an Edge Machine Learning experiment that lets a Particle Photon 2 control an external device by recognizing a spoken keyword.
A PDM MEMS microphone continuously captures audio at 16 kHz. An Edge Impulse keyword-spotting model runs locally on the Photon 2 and, when the target class exceeds an 80% confidence threshold, toggles a relay connected to D3.
The inference itself runs on the microcontroller; no cloud audio processing is required.
- ποΈ PDM digital microphone input
- π§ Edge Impulse keyword spotting
- π¬ MFCC audio processing
- β‘ On-device continuous inference
- π― 80% detection threshold
- π Voice-controlled relay toggle
- π‘ Particle Photon 2
- π§© Particle Workbench / Visual Studio Code workflow
- π³ Optional Docker build process
- π Public Edge Impulse project
flowchart LR
VOICE["π£οΈ Voice Command"]
MIC["ποΈ PDM Microphone"]
P2["Particle Photon 2"]
MFCC["MFCC"]
ML["π§ Edge Impulse"]
CHECK{"Target > 80%?"}
RELAY["β‘ Relay D3"]
MACHINE["βοΈ External Device"]
VOICE --> MIC
MIC --> P2
P2 --> MFCC
MFCC --> ML
ML --> CHECK
CHECK -->|"Yes"| RELAY
RELAY --> MACHINE
The microphone stream is processed continuously on the Photon 2.
The project uses the Particle Photon 2, based on the P2 module.
| Specification | Value |
|---|---|
| MCU | Realtek RTL8721DM |
| CPU | Arm Cortex-M33 |
| Clock | 200 MHz |
| User application space | 2 MB |
| RAM available to applications | 3 MB |
| Flash file system | 2 MB |
| Wi-Fi | 2.4 + 5 GHz |
| Bluetooth | BLE 5 |
| Battery support | LiPo charger / JST-PH |
The additional memory compared with earlier Particle boards makes the Photon 2 suitable for embedded ML workloads.
Official documentation:
π Photon 2 Datasheet
The original build uses the PDM MEMS microphone included with Particle's Edge ML Kit.
Connections:
| PDM Microphone | Photon 2 |
|---|---|
| GND | GND |
| 3V | 3V3 |
| CLK | A0 |
| DAT | A1 |
| SEL | Not connected |
The Photon 2 exposes PDM clock and data on these pins.
The source initializes audio as signed 16-bit data at:
Microphone_PDM::instance()
.withOutputSize(
Microphone_PDM::OutputSize::SIGNED_16
)
.withRange(
Microphone_PDM::Range::RANGE_32768
)
.withSampleRate(16000)
.init();So the audio pipeline operates at:
16,000 samples / second
16-bit signed samples
Relay wiring:
| Relay | Photon 2 |
|---|---|
| GND | GND |
| VCC | VCC |
| Signal | D3 |
Firmware:
pinMode(3, OUTPUT);The original relay module uses active-low logic.
digitalWrite(3, HIGH);digitalWrite(3, LOW);At startup:
int machineOn = 0;
digitalWrite(3, HIGH);so the machine begins in the OFF state.
The current source uses:
float detectionLimit = 0.8;A target classification therefore has to exceed:
80%
before the relay state is changed.
The relevant logic is:
if (
strstr(
result.classification[ix].label,
"muted"
)
&&
result.classification[ix].value >
detectionLimit
) {The relay then toggles:
if (machineOn == 1) {
digitalWrite(3, HIGH);
machineOn = 0;
}
else {
digitalWrite(3, LOW);
machineOn = 1;
}There are three names associated with the project artifacts.
The official Edge Impulse tutorial describes training:
machine
background
The current public dataset exposes:
turnoff
background
The code checks:
muted
This comes from the Particle βYouβre Mutedβ ML example used as the basis for the Photon 2 implementation.
Before compiling a newly exported model, change:
"muted"to the actual target label used in that model.
For example:
if (
strstr(
result.classification[ix].label,
"turnoff"
)
&&
result.classification[ix].value >
detectionLimit
)Particle reference:
π You're Muted β Particle Machine Learning Tutorial
Public project:
π Photon 2 Keyword Spotting β Edge Impulse Project #288386
The current public project contains:
| Parameter | Value |
|---|---|
| Samples | 60 |
| Audio collected | 1 minute |
| Sample rate | 16 kHz |
| Classes | background, turnoff |
| Validation accuracy | 100% |
| Test accuracy | 100% |
The Edge Impulse dashboard currently has a different target selected for its generic performance estimate, so the latency shown there should not be interpreted as measured Photon 2 latency.
The original tutorial recorded two long samples:
Target keyword
Background sound
The recordings were split into:
1000 ms
segments.
Impulse configuration:
| Parameter | Value |
|---|---|
| Window size | 1000 ms |
| Window increase | 500 ms |
| Processing block | Audio MFCC |
| Learning block | Classification (Keras) |
Workflow:
flowchart LR
VOICE["ποΈ Voice Samples"]
BG["π Background"]
SPLIT["1-second Samples"]
MFCC["MFCC Features"]
NN["Keras Classifier"]
LIB["Particle Library"]
P2["Photon 2"]
VOICE --> SPLIT
BG --> SPLIT
SPLIT --> MFCC
MFCC --> NN
NN --> LIB
LIB --> P2
Complete tutorial:
π Recognize Voice Commands with the Particle Photon 2 β Edge Impulse
The application uses:
#define EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW 4With the original:
1000 ms model window
this means each inference slice represents approximately:
250 ms
The application calls:
run_classifier_continuous(
&signal,
&result,
debug_nn
);rather than waiting for separate one-second recordings.
Conceptually:
flowchart LR
AUDIO["ποΈ Continuous Audio"]
S1["250 ms"]
S2["250 ms"]
S3["250 ms"]
S4["250 ms"]
MODEL["π§ 1 s Model Window"]
RESULT["Classification"]
AUDIO --> S1
AUDIO --> S2
AUDIO --> S3
AUDIO --> S4
S1 --> MODEL
S2 --> MODEL
S3 --> MODEL
S4 --> MODEL
MODEL --> RESULT
flowchart TD
START["Power On"]
OFF["βοΈ Relay OFF"]
MIC["ποΈ Capture PDM Audio"]
ML["π§ Continuous Inference"]
CHECK{"Target > 0.8?"}
STATE{"Machine state?"}
ON["Relay LOW β ON"]
STOP["Relay HIGH β OFF"]
START --> OFF
OFF --> MIC
MIC --> ML
ML --> CHECK
CHECK -->|"No"| MIC
CHECK -->|"Yes"| STATE
STATE -->|"OFF"| ON
STATE -->|"ON"| STOP
ON --> MIC
STOP --> MIC
| Component | Quantity |
|---|---|
| Particle Photon 2 | 1 |
| PDM MEMS microphone | 1 |
| Single-channel relay module | 1 |
| Breadboard | 1 |
| Jumper wires | Several |
| USB-C cable | 1 |
The microphone used in the original build came from the Particle Edge ML Kit.
Particle Machine Learning resources:
π Particle Machine Learning
Unlike most Arduino projects in this repository collection, the Photon 2 application is built using:
π Visual Studio Code
with:
π Particle Workbench
Particle Workbench provides:
- Device OS toolchains
- Local compilation
- Cloud compilation
- Device flashing
- Particle library management
- C++ IntelliSense
git clone \
https://github.com/ronibandini/Photon2VoiceCommand.git
cd Photon2VoiceCommandRepository:
π github.com/ronibandini/Photon2VoiceCommand
Main source:
π main.cpp
Install:
π Visual Studio Code
then:
π Particle Workbench
Particle Workbench runs on:
Windows
Linux
macOS
Open:
π Edge Impulse Project #288386
or create your own keyword project.
Then:
Deployment
β Particle Library
β Build
Unzip the downloaded project.
In VS Code:
Ctrl/Cmd + Shift + P
select:
Particle: Import Project
and choose:
project.properties
Replace the generated:
src/main.cpp
with:
π main.cpp
The repository currently includes:
#include \
<Photon_2_Keyword_keyword_spotting_inferencing.h>The exact header name depends on the Edge Impulse export.
If your project generates another name, replace this include accordingly.
The current repository looks for:
"muted"Replace it with the class contained in your exported model.
For the current public project, that is:
"turnoff"The original documented configuration is:
Device OS: 5.5.0
Platform: P2
Particle uses the P2 platform target for both P2 and Photon 2 builds.
Current Photon 2 documentation requires Device OS 5.0.0 or later.
If Workbench reports that the microphone library is missing:
Particle:
Install Library
install:
Microphone_PDM@0.0.2
From the Particle Command Palette:
Particle:
Flash Application & Device (local)
Serial debug output runs at:
115200 baud
Startup:
Particle Photon 2 voice operated machine
Roni Bandini, September 2023
Machine is off
The original project documentation also includes a Docker workaround for the:
Argument list too long
build error.
Example for Device OS 5.5.0:
docker pull \
particle/buildpack-particle-firmware:5.5.0-p2Build:
docker run \
--name=photon2-build \
-v /absolute/project/path:/input \
-v /absolute/project/path:/output \
-e PLATFORM_ID=32 \
particle/buildpack-particle-firmware:5.5.0-p2The output is:
firmware.bin
Then flash through Particle CLI.
Particle build documentation:
π Particle Firmware Build Options
The repository is deliberately minimal:
Photon2VoiceCommand/
β
βββ main.cpp
βββ README.md
- ποΈ
main.cppβ PDM capture, Edge Impulse inference and relay control - π
README.mdβ original installation notes
The trained model itself is hosted through Edge Impulse rather than stored directly in this repository.
Complete official tutorial covering circuit, dataset acquisition, MFCC processing, training, deployment and Particle Workbench setup:
π Recognize Voice Commands with the Particle Photon 2
Dataset and trained keyword-spotting model:
π Photon 2 Keyword Spotting β Project #288386
Photon 2 hardware:
π Photon 2 Datasheet
Development environment:
π Particle Workbench
Machine Learning:
π Particle Machine Learning
Original code base used as a reference:
π You're Muted β Particle
Audio classification with Edge Impulse followed by a physical/network action when the target audio class is detected.
π github.com/ronibandini/reggaetonBeGone
Audio TinyML on Arduino Nano 33 BLE Sense for detecting the sound of paper page turns.
π github.com/ronibandini/ReadingTime
TinyML event recognition with local inference and an external physical/reporting workflow.
π github.com/ronibandini/BTFall
Edge Impulse inference linked to physical relay/traffic-light control.
π github.com/ronibandini/TIAM62AITrafficLight
Contracultura Maker is a book by Roni Bandini about maker culture, experimental electronics, AI, physical computing and technological autonomy.
π Contracultura Maker β GitHub repository
π Download Contracultura Maker PDF
Roni Bandini Maker Β· AI Developer Β· Writer Buenos Aires, Argentina
- π GitHub β @ronibandini
- π Medium β @ronibandini
- π X / Twitter β @RoniBandini
- πΈ Instagram β @ronibandini
βΆοΈ YouTube β @RoniBandini- πΌ LinkedIn β Roni Bandini
Built with ποΈ + Photon 2 + Edge Impulse + keyword spotting.