TinyML: Edge Impulse Workflow

The manual pipeline from chapters 4-8 gives you full control. Edge Impulse trades that control for speed: from data collection to deployed firmware in an afternoon, without writing the preprocessing code yourself.

What Edge Impulse Is

Edge Impulse is a cloud platform (as of 2026, with a generous free tier) that wraps the TinyML pipeline behind a browser UI. You handle:

  • Choosing the sensor and labeling strategy
  • Reviewing data quality
  • Selecting the model architecture
  • Setting the deployment target

Edge Impulse handles:

  • Feature extraction (MFCC, spectral features, image resizing)
  • Model training with hyperparameter tuning
  • Quantization and optimization
  • Generating ready-to-compile Arduino/ESP-IDF/Zephyr libraries

It is worth knowing because many real projects use it, and because its generated code is instructive even when you don't use it.

Creating a Project

Sign up at studio.edgeimpulse.com and create a new project.

Project type:    Classification
Device:          Arduino Nano 33 BLE Sense (or choose your target)

The device selection changes default DSP block choices and memory budget estimates.

Connecting the Device for Live Collection

Edge Impulse has a CLI that turns your board into a streaming data source:

npm install -g edge-impulse-cli

edge-impulse-daemon
# Follow the prompts: paste your API key, select the project
# The CLI flashes a data forwarder sketch to the board automatically

After connecting, you see the device in the Edge Impulse dashboard under Devices. Click Start sampling to record labeled samples directly into the project without writing a Python collector.

For custom sensors or serial data formats, the edge-impulse-data-forwarder command accepts CSV lines on stdin:

# Forward data from a custom sketch that prints "aX,aY,aZ" to Serial
edge-impulse-data-forwarder --frequency 100 --port /dev/ttyACM0

Building an Impulse

An "impulse" is the processing pipeline from raw data to classification.

Navigate to Impulse Design in the left sidebar:

  1. Input block: set window size (1000 ms) and window increase (200 ms for 5 Hz inference).
  2. Processing block: choose the DSP block.
    • For accelerometer: "Spectral Analysis" or "Raw features"
    • For audio: "MFCC" or "MFE" (mel filterbank energy, no DCT)
    • For image: "Image" (resize + normalize)
  3. Learning block: choose "Classification (Keras)" for standard classification.
  4. Click Save Impulse.

Configuring the DSP Block

For gesture classification with accelerometer data, use "Spectral Analysis":

Scale axes:    1
Input decimation sampling:  1
Type:          FFT
FFT length:    16
Overlap:       50%
Noise floor:   -52 dB

Click Save parameters, then Generate features. The dashboard shows a feature visualization (a scatter plot of all samples by class). Well-separated clusters predict good model accuracy.

Training the Model

Navigate to NN Classifier (or Transfer Learning for image models):

Number of training cycles:  100
Learning rate:              0.0005
Validation set size:        20%
Auto-balance dataset:       enabled

Click Start training. Training runs on Edge Impulse's servers and typically finishes in 1-5 minutes for sensor models.

After training, the results page shows:

Accuracy:         94.8%
Loss:             0.18
RAM (peak):       3.4 KB
Flash (program):  16.1 KB
Latency (Nano):   2.3 ms

The RAM and flash estimates are measured on the target hardware, not estimated. This is one of Edge Impulse's most useful features: you know whether the model fits before flashing.

Exporting as an Arduino Library

Navigate to Deployment:

  1. Select Arduino library
  2. Click Build
  3. Download the generated .zip file

Install it in Arduino IDE:

arduino-cli lib install --zip ~/Downloads/ei-gesture-project-arduino-1.0.4.zip

The library contains the full inference pipeline: DSP feature extraction, the TFLite model, and a C++ class that wraps run_classifier(). The sketch is minimal:

// ei_gesture.ino
#include <gesture-project_inferencing.h>
#include <Arduino_LSM6DSOX.h>

float features[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];
int   feature_ix = 0;

int raw_feature_get_data(size_t offset, size_t length, float* out_ptr) {
  memcpy(out_ptr, features + offset, length * sizeof(float));
  return 0;
}

void setup() {
  Serial.begin(115200);
  IMU.begin();
}

void loop() {
  if (!IMU.accelerationAvailable()) return;

  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) return;
  feature_ix = 0;

  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) return;

  // Print top prediction
  for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
    Serial.print(result.classification[i].label);
    Serial.print(": ");
    Serial.println(result.classification[i].value, 3);
  }
  Serial.println();
}

The run_classifier() function does the DSP and inference in one call. The Arduino library includes the normalization, windowing, feature extraction, and TFLite Micro inference under the hood.

Continuous Motion Recognition

Edge Impulse has a special mode for streaming inference that handles the sliding window automatically. Enable it by selecting Continuous motion recognition in the deployment options.

The generated library provides run_classifier_continuous() which you call with each new sensor reading. It maintains the internal ring buffer and feature pipeline:

void loop() {
  if (!IMU.accelerationAvailable()) return;

  float ax, ay, az;
  IMU.readAcceleration(ax, ay, az);

  // Buffer single sample
  float buf[3] = {ax, ay, az};
  signal_t signal;
  int err = numpy::signal_from_buffer(buf, 3, &signal);
  if (err != 0) return;

  ei_impulse_result_t result;
  bool debug = false;
  run_classifier_continuous(&signal, &result, debug);

  if (result.bounding_boxes_count > 0) {
    // If using object detection: skip for classification
  } else {
    // Classification result
    ei_classifier_smooth_print_results(&result, SMOOTH_LABELS,
        EI_CLASSIFIER_LABEL_COUNT, 0.8f);
  }
}

When to Use Edge Impulse vs. Manual Pipeline

SituationRecommendation
Prototyping a new gesture or keywordEdge Impulse: faster iteration
Need custom DSP not in Edge ImpulseManual pipeline
Production with strict size limitsManual + custom MicroMutableOpResolver
Multiple people collecting dataEdge Impulse's UI handles this well
Need to understand the full stackManual pipeline at least once
Deploying to Zephyr or bare metalEdge Impulse SDK or manual TFLite Micro

Edge Impulse's generated code is a good reference even when you write your own. The DSP implementations in edge-impulse-sdk/dsp/ are readable and correct.

Exporting the Model for Use Outside Edge Impulse

If you want the trained model without the Edge Impulse wrapper:

  1. Navigate to Impulse Design → NN Classifier → Export model
  2. Select TensorFlow Lite (int8 quantized)
  3. Download model.tflite

From there you can follow the manual deployment steps in chapter 7. The model is a standard TFLite flat buffer.

Next Steps

Continue to 11-optimization.md to reduce inference latency, model size, and power consumption using pruning, quantization-aware training, and op selection.