ElectronLab Project Lab
TinyML & Edge AI TensorFlow Lite Neural Networks

Edge AI Handwritten Digit Recognizer on Embedded Microcontrollers

Deploy lightweight quantized convolutional neural networks onto an embedded microcontroller to perform real-time classification of handwritten digits (0-9) without relying on internet or cloud APIs.

Reading Time 14 Minutes
Difficulty Level Advanced
Hardware Platform Arduino Nano 33 BLE / ESP32

1. Aim of the Project

Project Objective

The aim is to train a 28x28 grayscale MNIST convolutional neural network in Python using TensorFlow, convert and quantize the model parameters into 8-bit integers (INT8) using TensorFlow Lite for Microcontrollers (TFLM), embed the static C byte array into firmware, and execute inference in under 30 milliseconds directly on edge silicon.

2. Interactive 3D Assembly & Circuit Wiring Model

Rotate the 3D model 360 degrees, zoom in/out, disassemble/explode parts to inspect individual hardware layers, toggle realistic circuit wires, and click any component to inspect its engineering specifications.

3D Hardware Simulation & Assembly Lab
Left Click + Drag: Rotate Orbit (360°)
Right Click + Drag: Pan Scene
Scroll: Zoom In / Out
Click Component: Inspect Hardware Details
Hovered Component

3. Laboratory Video Masterclass Tutorial

Step-by-step video walkthrough covering breadboard circuit assembly, wiring verification, and testing. Enrolled students and instructors can access video streaming below.

2. Learning Outcomes

Model Quantization (INT8)

Learn how floating-point 32-bit weights are compressed into 8-bit integers to fit inside strictly constrained microcontroller SRAM.

Zero-Latency Edge Inference

Understand the security, power, and latency advantages of running machine learning offline directly on sensor endpoints.

3. Technologies Learned

TensorFlow Lite for Microcontrollers (TFLM)
Convolutional Layers & Max Pooling
Softmax Output Probabilities

4. Required Components

Component Name Quantity Specification Interface
Arduino Nano 33 BLE / ESP32 1 Arm Cortex-M4 or Xtensa dual-core with >256KB RAM USB / 3.3V
0.96 inch I2C OLED Display 1 128x64 SSD1306 Graphic Display I2C (SDA, SCL)
Touchpad or Camera Module 1 OV7670 camera or capacitive input pad Parallel / I2C

5. Complete Arduino Edge AI Code

edge_digit_inference.ino
/*
 * Project: Edge AI Handwritten Digit Recognizer
 * Author: ElectronLab STEM Curriculum
 * Framework: TensorFlow Lite for Microcontrollers
 */

#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/all_ops_resolver.h>
#include <tensorflow/lite/micro/micro_error_reporter.h>
#include <tensorflow/lite/micro/micro_interpreter.h>
#include <tensorflow/lite/schema/schema_generated.h>

#include "digit_model_data.h" // Quantized model byte array

namespace {
  tflite::ErrorReporter* error_reporter = nullptr;
  const tflite::Model* model = nullptr;
  tflite::MicroInterpreter* interpreter = nullptr;
  TfLiteTensor* input = nullptr;
  TfLiteTensor* output = nullptr;

  constexpr int kTensorArenaSize = 60 * 1024; // 60KB Arena
  uint8_t tensor_arena[kTensorArenaSize];
}

void setup() {
  Serial.begin(115200);
  while (!Serial);

  static tflite::MicroErrorReporter micro_error_reporter;
  error_reporter = µ_error_reporter;

  // Load the quantized TFLite flatbuffer model
  model = tflite::GetModel(g_digit_model_data);
  
  static tflite::AllOpsResolver resolver;
  static tflite::MicroInterpreter static_interpreter(
      model, resolver, tensor_arena, kTensorArenaSize, error_reporter);
  interpreter = &static_interpreter;

  // Allocate memory from tensor arena for model tensors
  TfLiteStatus allocate_status = interpreter->AllocateTensors();
  if (allocate_status != kTfLiteOk) {
    TF_LITE_REPORT_ERROR(error_reporter, "AllocateTensors() failed");
    return;
  }

  input = interpreter->input(0);
  output = interpreter->output(0);

  Serial.println("Edge AI Digit Recognizer Initialized!");
}

void loop() {
  // Feed normalized 28x28 image buffer into input tensor
  // (In real testing, populate input->data.f from sensor or serial)
  
  // Run inference
  TfLiteStatus invoke_status = interpreter->Invoke();
  if (invoke_status != kTfLiteOk) {
    TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed!");
    return;
  }

  // Find predicted class with highest confidence
  int bestDigit = -1;
  float maxConfidence = 0.0;
  
  for (int i = 0; i < 10; i++) {
    float confidence = output->data.f[i];
    if (confidence > maxConfidence) {
      maxConfidence = confidence;
      bestDigit = i;
    }
  }

  Serial.print("Recognized Digit: ");
  Serial.print(bestDigit);
  Serial.print(" (Confidence: ");
  Serial.print(maxConfidence * 100.0, 1);
  Serial.println("%)");

  delay(3000);
}

6. Working Principle

The convolutional neural network applies 3x3 kernel matrix convolutions across the 28x28 pixel grid, extracting spatial features like vertical strokes, loops, and intersections. The flattened feature maps pass through dense fully connected layers, culminating in a 10-node softmax output representing probabilities for digits 0 through 9.

7. Troubleshooting Guide

Arena Allocation Failed

Your microcontroller ran out of RAM. Increase kTensorArenaSize if supported by your board, or prune model filter counts during Python training.