⚡ Edge AI & TinyML

Implementing TinyML on ARM Cortex-M: Edge Anomaly Detection with TensorFlow Lite Micro

Streaming raw high-frequency sensor telemetry to the cloud for cloud-based machine learning inference is costly, latency-prone, and unsustainable for remote battery-powered assets. TinyML (Tiny Machine Learning) brings neural network inference directly onto sub-$3 microcontrollers (ARM Cortex-M4, M7, and M33). By executing inference locally, edge sensors achieve sub-millisecond anomaly detection while extending battery lifespans to multiple years.

1. Model Quantization: From 32-Bit Float to Signed INT8

Deep learning models are trained in frameworks like PyTorch or TensorFlow using 32-bit floating-point (FP32) arithmetic. However, microcontrollers without hardware double-precision FPUs suffer steep performance penalties evaluating floating-point mathematics.

INT8 Symmetric Post-Training Quantization maps continuous floating-point weights and activations into discrete signed 8-bit integers $[-128, 127]$ according to the affine transformation:

r = S × (q - Z)
where:
• r = Real floating-point value
• q = Quantized 8-bit integer [-128, 127]
• S = Positive floating-point Scale factor
• Z = Integer Zero-point offset (Z = 0 for symmetric quantization)

During quantization in Python, a representative calibration dataset is passed through the TensorFlow Lite Converter. The converter analyzes the dynamic range of intermediate activations, locking in fixed integer scales:

import tensorflow as tf

# Convert trained Keras model to full INT8 TFLite model
converter = tf.lite.TFLiteConverter.from_saved_model('models/anomaly_autoencoder')
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset_generator
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8

tflite_quant_model = converter.convert()
with open('model_quant_int8.tflite', 'wb') as f:
    f.write(tflite_quant_model)

The resulting flatbuffer is converted into a C byte array using xxd -i, ready to be linked directly into internal microcontroller flash memory.

2. CMSIS-NN SIMD Assembly Optimization

ARM Cortex-M4 and Cortex-M7 cores feature an integrated DSP extension containing Single Instruction Multiple Data (SIMD) instructions operating on 32-bit registers. ARM's CMSIS-NN library replaces generic C matrix multiplications with hand-optimized assembly kernels:

  • Dual 16-Bit MAC (SMLAD): Loads two packed 16-bit integers from memory and executes two parallel multiply-accumulate operations in a single processor cycle.
  • 4-Channel Vectorized Unrolling: Inner loops of convolution and fully connected layers are unrolled by a factor of 4, maximizing pipeline utilization and eliminating branching overhead.
  • Fast Fixed-Point Shift Arithmetic: Quantized scaling factors ($S_1 imes S_2 / S_3$) are evaluated via integer multiplication and bitwise arithmetic shifts, eliminating software float routines entirely.

On an STM32H7 (ARM Cortex-M7 running at 480 MHz), CMSIS-NN reduces the inference latency of a 3-layer anomaly autoencoder from 18.4 milliseconds down to just 3.7 milliseconds, representing an 80% reduction in active CPU energy consumption.

3. Static Memory Management: The Tensor Arena Pattern

In safety-critical embedded systems, dynamic memory allocation via malloc() is strictly prohibited due to heap fragmentation risks. TensorFlow Lite Micro implements the Tensor Arena pattern, where all execution memory is pre-allocated as a static array:

#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "model_data.h"

// Statically allocated memory pool in internal SRAM (e.g., 32 KB)
constexpr int kTensorArenaSize = 32 * 1024;
static uint8_t tensor_arena[kTensorArenaSize] __attribute__((aligned(16)));

void init_tinyml_engine() {
    const tflite::Model* model = tflite::GetModel(g_anomaly_model_data);
    static tflite::MicroMutableOpResolver<4> resolver;
    resolver.AddFullyConnected();
    resolver.AddRelu();
    resolver.AddQuantize();
    resolver.AddDequantize();

    static tflite::MicroInterpreter interpreter(
        model, resolver, tensor_arena, kTensorArenaSize);

    TfLiteStatus allocate_status = interpreter.AllocateTensors();
    if (allocate_status != kTfLiteOk) {
        // Handle Tensor Arena overflow
        while (1);
    }
}

The interpreter calculates tensor lifetimes and overlaps intermediate activation buffers, allowing complex multi-layer models to execute comfortably within small SRAM budgets.

4. Edge Feature Extraction: CMSIS-DSP FFT Preprocessing

Feeding raw time-domain accelerometer samples directly into a neural network requires massive model architectures. Instead, industrial vibration monitoring uses frequency-domain preprocessing executed via the ARM CMSIS-DSP library:

[3-Axis Accelerometer @ 3.2 kHz] → [Hanning Window] → [CMSIS-DSP RFFT (1024 pts)] → [Spectral Energy Bins] → [TFLM INT8 Autoencoder]

A 1024-point Real FFT converts 1024 vibration samples into 512 discrete frequency bins. The firmware aggregates these into 32 spectral energy bands, reducing neural network input dimensionality by 97% and allowing an ultra-compact 32-16-8-16-32 autoencoder to detect bearing degradation with 99.2% accuracy.

5. Embedded Machine Learning Framework Comparison

Edge ML Runtime Min Flash Footprint Min RAM Overhead Hardware Acceleration Dynamic Memory Alloc
TensorFlow Lite Micro (TFLM) 20 KB - 50 KB 8 KB - 32 KB CMSIS-NN / Ethos-U Zero (Static Arena)
Edge Impulse (EON Compiler) 15 KB - 35 KB 6 KB - 20 KB CMSIS-NN / DSP Zero (Direct C++)
ONNX Runtime Mobile 1.5 MB - 4 MB 2 MB - 8 MB NEON / GPU / NPU Heap Malloc Required

6. Key Engineering Invariants for Edge Inference

  • Symmetric Quantization Range Protection: Ensure the representative dataset contains realistic sensor minimums and maximums under all environmental extremes. Out-of-bounds input values will clip against -128 or +127, causing severe inference degradation.
  • Memory Alignment (16-Byte Bounds): CMSIS-NN SIMD instructions require input pointers and tensor memory buffers to be aligned to 16-byte memory boundaries. Always decorate the Tensor Arena with __attribute__((aligned(16))).
  • Dual-Core Workload Segregation: On dual-core microcontrollers (e.g., STM32H747 dual-core Cortex-M7 + M4), assign real-time ADC sampling and motor PWM timing to the Cortex-M4 core, delegating compute-intensive TFLite inference to the high-clocked Cortex-M7 core.
Robert Baindourov

Written & Architected by Robert Baindourov

Senior IoT Firmware & Edge-Cloud Solutions Architect with 17+ years total experience (15+ years professional architecture). Specializing in bare-metal C/C++, FreeRTOS, Zephyr OS, Embedded Rust, hardware secure elements (ATECC608, TPM 2.0), and high-throughput cloud telemetry pipelines (AWS IoT Core, EMQX, Node.js). Based in Los Angeles, CA.