Edge Impulse is the most complete end-to-end platform for building TinyML models and deploying them to embedded hardware. What once required deep expertise in signal processing, neural network design, quantization, and embedded C++ can now be accomplished in a structured workflow that handles each step — and it is accessible to firmware engineers who have never written a Python training script in their lives.
This tutorial walks through the entire process of building a motion gesture recognizer that runs on a microcontroller. You will go from a blank project to inference running on hardware in under two hours. The same methodology applies to any sensor modality: audio keyword spotting, vibration anomaly detection, image classification, or multi-sensor fusion.
What You Will Build
A gesture recognizer that classifies three wrist motions — idle, wave, and punch — using a 3-axis accelerometer. The model will run locally on a development board, classify gestures in under 20 ms, and achieve over 90% accuracy on held-out test data. No cloud connectivity is needed during inference.
Hardware required: Any of the following are natively supported by Edge Impulse:
- Arduino Nano 33 BLE Sense
- Nordic Thingy:53
- SparkFun MicroMod (with ML Carrier Board)
- STM32 Nucleo (via the Edge Impulse CLI)
- Espressif ESP32-S3-BOX
Step 1: Create Your Edge Impulse Project
- Go to edgeimpulse.com and create a free account
- Click Create new project, name it
gesture-recognizer - Under Project type, select Accelerometer data
- Connect your development board by installing the Edge Impulse CLI:
npm install -g edge-impulse-cli - Run
edge-impulse-daemonin your terminal — it will detect the board and link it to your project
Within 5 minutes of account creation, your hardware is recognized and ready to collect training data.
Step 2: Collect Training Data
Data quality is the single largest determinant of model quality. For a gesture recognizer, you need:
- Minimum: 2 minutes of labeled data per class (idle, wave, punch)
- Recommended: 5+ minutes per class, collected by multiple people, in multiple environments
- Balance: Keep class counts within 20% of each other to avoid class imbalance issues
Collection process:
- In Edge Impulse Studio, go to Data acquisition
- Select your connected device and IMU as the sensor
- Set sample rate to 100 Hz (sufficient for gesture recognition, keeps model small)
- Enter label
idleand collect 2 minutes of data with the device stationary or in natural hand position - Repeat with labels
wave(repeated wave motions) andpunch(quick forward thrust motions)
Pro tips for data collection:
- Vary the speed, intensity, and axis orientation of each gesture — this improves model robustness
- Record some ambiguous gestures (slow waves, partial punches) to give the model realistic boundary examples
- Include noise samples (phone in pocket, holding a coffee cup) in the
idleclass
Step 3: Design the Impulse (Feature Extraction Pipeline)
The Impulse is Edge Impulse’s term for the signal processing and ML pipeline. Click Create impulse in the left sidebar.
Time series input block:
- Window size:
2000 ms(captures one complete gesture) - Window increase:
200 ms(80% overlap during inference — ensures no gesture is missed between windows) - Frequency:
100 Hz - Axes: X, Y, Z (all three accelerometer axes)
Processing block (feature extraction): Select Spectral Analysis. This block computes FFT-based spectral power features from the time-series window. For accelerometer gesture recognition, spectral features outperform raw time-domain features because gestures have characteristic frequency content independent of exact timing.
Key settings:
- Scale axes: checked (normalizes axis magnitudes)
- Spectral power edges: 0.5, 1, 2, 5, 10 Hz (captures DC components and low-frequency gesture content)
- Noise floor (dB): -40
Learning block: Select Classification (Keras). This adds a neural network classifier after the spectral feature extractor.
Click Save Impulse.

Step 4: Configure the Neural Network
Go to NN Classifier in the left sidebar. Edge Impulse will show you the input feature dimension (typically 33 features from three-axis spectral analysis) and a default architecture.
Default architecture (works well for simple gestures):
Input (33 features)
→ Dense(20, activation='relu')
→ Dropout(0.1)
→ Dense(10, activation='relu')
→ Dense(3, activation='softmax') ← 3 output classes
Training settings:
- Number of training cycles: 100 (epochs)
- Learning rate: 0.0005
- Validation set size: 20%
- Data augmentation: enable Noise and Shift (improves robustness to real-world variation)
Click Start training. Training takes 10–30 seconds in Edge Impulse’s cloud compute infrastructure.
Reading the results: After training, Edge Impulse shows a confusion matrix and accuracy/loss curves. A well-trained gesture recognizer should show:
- Overall accuracy: 90%+
- No single class below 85% recall
- Training/validation accuracy curves converging (no severe overfitting)
If accuracy is below 90%, common causes are:
- Insufficient training data (collect more)
- Overlapping gestures (refine gesture definitions or collect cleaner examples)
- Noise in
idleclass (ensure idle captures full environmental range)
Step 5: Deploy as an Arduino Library or C++ SDK
Go to Deployment in the left sidebar. Edge Impulse supports over 60 deployment targets. For Arduino:
- Select Arduino library
- Check Enable EON Compiler — this optimizes the model to minimize RAM and CPU usage
- Click Build
- Download the
.ziplibrary and add it to Arduino IDE via Sketch → Include Library → Add .ZIP Library
For non-Arduino embedded targets (STM32 with STM32CubeIDE, NXP MCUXpresso, bare-metal CMake):
- Select C++ library under Deployment
- This generates a self-contained SDK with:
edge-impulse-sdk/— the inference runtimetflite-model/— the quantized model as a C arraymodel-parameters/— signal processing parameters- Example
main.cppshowing the inference loop
Step 6: Write the Inference Loop
For Arduino targets, Edge Impulse generates a complete example sketch. The core inference pattern:
#include <gesture_recognizer_inferencing.h> // generated by Edge Impulse
// Buffer to hold one inference window
float features[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];
int feature_ix = 0;
void loop() {
// Fill features buffer from accelerometer
float ax, ay, az;
IMU.readAcceleration(ax, ay, az);
features[feature_ix++] = ax;
features[feature_ix++] = ay;
features[feature_ix++] = az;
if (feature_ix == EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
feature_ix = 0;
// Run classifier
signal_t signal;
numpy::signal_from_buffer(features, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
ei_impulse_result_t result;
EI_IMPULSE_ERROR err = run_classifier(&signal, &result, false);
if (err == EI_IMPULSE_OK) {
// Find highest confidence class
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
Serial.printf("%-12s: %.5f\n", result.classification[ix].label,
result.classification[ix].value);
}
}
}
}
The run_classifier() function handles all signal processing (spectral feature extraction) and neural network inference internally. The API is clean and consistent regardless of what model architecture or DSP pipeline is inside.
Step 7: Optimize for Production Deployment
The tutorial above creates a working prototype. Production deployment needs additional steps:
1. Live classification with sliding window
The example above fills a buffer then classifies. For real-time gesture detection, use Edge Impulse’s continuous inference mode, which maintains a circular buffer and classifies every window_increase milliseconds.
2. Output filtering Raw classifier outputs are noisy. Apply a simple exponential moving average or a majority-vote over the last N windows to reduce false triggers:
// Exponential moving average smoothing
smoothed_scores[i] = 0.7 * smoothed_scores[i] + 0.3 * result.classification[i].value;
3. Confidence thresholding Only act on classifications above a confidence threshold (e.g., 0.85). Below threshold, output “uncertain” rather than a low-confidence class label.
4. Model retraining with field data After deployment, collect misclassified samples via a feedback mechanism and retrain. Edge Impulse’s Bring Your Own Model feature supports incremental retraining workflows.
5. OTA model updates Use your device’s OTA update mechanism to deploy updated model weights when the model is retrained. Edge Impulse’s EON compiler generates models as constant data arrays that can be placed in flash at a defined address, enabling OTA replacement without reflashing the entire firmware.
From Tutorial to Production
For more complex AIoT systems, see our companion articles:
- TinyML Explained — foundations of on-device ML
- Running AI on Edge Devices — deep dive on quantization and framework selection
Edge Impulse’s extensive documentation covers advanced topics including multi-axis sensor fusion, synthetic data generation, and deploying to production-grade hardware like the Nordic nRF9160 DK.
Conclusion
Edge Impulse dramatically lowers the barrier to TinyML development. The workflow from data collection through hardware deployment is coherent, well-documented, and — with the right approach — achievable in an afternoon. The production challenges — continuous inference, output filtering, OTA updates, and model retraining at scale — are real, but they are engineering problems with known solutions.
If you need to take a TinyML project from prototype to production at scale, UABit’s embedded ML team has the expertise to help. We work with Edge Impulse, TFLM, and custom inference pipelines across a wide range of MCU targets, and we can help you design a deployment architecture that handles fleet management, OTA model updates, and accuracy monitoring in production.
IoT & AIoT Weekly
Get the best IoT development content delivered weekly. No noise, just signal.