PTENES
MODULE 3.4 · FUNDAMENTALS

🧠 AI inside the microcontroller

In previous modules, AI ran on the Raspberry Pi 5. Here, it moves down to a chip that costs a few reais and uses just a few milliwatts: the ESP32-S3. You’ll learn what TinyMLis, why the model needs to be quantized to int8, and two ways to get there: Edge Impulse (without writing the network) and TensorFlow Lite Micro (full control).

6
Topics
Intermediate
Level
Foundation
Type
ESP32-S3
Suggested board
0%
0 of 0 topics read · Section 1 of 6

Detailed content

1

📍 Why AI inside the microcontroller

An AI model can run in three places: on a server on the internet, on a computer inside the robot (the Pi 5 from module 3.3), or on the microcontroller itself which already reads the sensors. The closer it is to the sensor, the lower the latency and power use, and no data leaves the robot. The trade-off is size: the ESP32-S3 can fit models ranging from tens to hundreds of kilobytes, which answer simple questions like "is this movement a wave?" or "is there a person in front?".

🌱 New here?

Model is the file containing the "knowledge" learned during training: a network of numbers called weights. Training means adjusting these weights using many examples, and it happens on a PC or in the cloud. Inference means using a trained model to answer questions about new data; that’s all the microcontroller does.

CLOUDhuge modelrequires internet100–1000 ms PI 5YOLO26, OpenCVa few wattstens of ms ESP32-S3KB-sized modelfraction of a watta few ms far from the sensor next to the sensor less latency →

How to read this map: From left to right, the model gets smaller and closer to the sensor. The highlighted box is the focus of this module. The times are orders of magnitude, not measurements from your board: always measure on your setup.

✓ Good for TinyML

  • ✓Classifying movement using the IMU (gestures, falls, motor vibrations).
  • ✓One or a few voice command words.
  • ✓"Person or no person" in a small grayscale image.
  • ✓A battery-powered robot that needs to react quickly without Wi-Fi.

✗ Leave these to the Pi or PC

  • ✗Detecting dozens of classes with boxes, like YOLO26.
  • ✗Understanding free-form sentences or having a conversation (that’s Track 5).
  • ✗Training the model: training never runs on the microcontroller.
  • ✗High-resolution color images, frame by frame.

Why learn this

Knowing where each model should run helps you avoid two costly mistakes: adding a Pi 5 to a project that an ESP32-S3 could handle on battery power, or trying to fit on the microcontroller a task that only a computer can handle. In the complete robot (modules 3.5 and 3.6), both work together: the Pi sees the world and the ESP32 reacts.

Key concepts

Inference
use the ready-made model
Training
on a PC or in the cloud
Latency
time to respond
Edge AI
AI next to the sensor
2

🗜️ TinyML: a small model, quantized to int8

TinyML is the term for machine learning that runs on microcontrollers. The ESP32-S3 has about 512 KB of internal RAM and a few megabytes of flash; many boards add 2 to 8 MB of external PSRAM. A model trained on a PC stores each weight as float32 (4 bytes). Quantization converts each weight to int8 (1 byte, from −128 to 127): the model becomes 4 times smaller and integer calculations run much faster, with only a small drop in accuracy when done well.

float32 · 4 bytes 0,4172 quantize int8 · 1 byte 53 integer = round(value ÷ scale) + zero point e.g., scale 0.0079 and zero point 0 → 0.4172 ÷ 0.0079 ≈ 53 400 KB model in float32 → about 100 KB in int8

How to read this map: each weight goes from taking up four boxes (bytes) to taking up one. The scale and zero point are stored in the model so it can be converted back when needed. The example numbers are illustrative.

🌱 New here?

Flash is the memory that stores the program and model even when the power is off. RAM is working memory, cleared when the device is turned off; this is where intermediate inference calculations are stored (the “arena”). PSRAM is extra, slower RAM soldered next to the chip on some boards, such as the XIAO ESP32-S3 Sense.

TaskTypical model size (int8)Fits on the S3?
IMU gesturea few to tens of KBYes, with room to spare
Voice keywordtens of KBYes
Person yes/no, 96×96 grayscalea few hundred KBYes, preferably with PSRAM
YOLO26n, 640 pxa few MBNo: use the Pi 5

Why learn this

When a TinyML project fails, it’s almost always because of memory: the model doesn’t fit in flash, or the arena doesn’t fit in RAM. Understanding that int8 is the standard format and knowing the approximate size of each task helps you choose the board and model before spending an afternoon compiling.

Key concepts

TinyML
AI on a microcontroller
Quantization
float32 → int8
Arena
RAM for calculations
PSRAM
Extra board RAM
3

🧪 Edge Impulse: from data to Arduino sketch

O Edge Impulse is a web platform with a free plan for personal projects. You send in sensor data, label it, choose processing and neural network blocks with a few clicks, train in the cloud, and download the result as an Arduino library in .zip format, already quantized. You don’t write the network; you only write the sketch that reads the sensor and calls the classifier.

1 collectsensor → PC 2 labelclass name 3 impulsewindow + blocks 4 trainand test 5 exportlibrary .zip test failed? go back and collect more examples of the class it confused

How to read this map: the five boxes are the Edge Impulse project tabs, in the order you move through them. The cyan loop shows what happens in practice: the first model almost always confuses two classes, and the solution is to collect more data, not change the network.

Step by step

1

Create the project and install the tool

Create an account at edgeimpulse.com and start a new project. On your PC, install Node.js, then the official CLI with npm install -g edge-impulse-cli (check the current instructions in the documentation).

2

Send the data over serial

Flash a sketch to the ESP32-S3 that prints readings separated by commas (the one from topic 5). Run edge-impulse-data-forwarder, sign in, and name the axes. The board will appear in the data collection tab.

3

Design the impulse and train

Choose the window size, a processing block (for an IMU, “Spectral Analysis”), and a classifier. Train the model, check the confusion matrix, and run the test using the data set aside for it.

4

Export and install the library

Under “Deployment,” choose “Arduino library” with int8 optimization and download the .zip. In Arduino IDE 2.x: Sketch → Include Library → Add .ZIP Library. The generated header uses your project name: <your_project>_inferencing.h.

💡 The first compile takes a while

The Edge Impulse library includes the entire inference engine as source code, so the first compile on the ESP32 takes several minutes. That’s normal. Later compiles use the IDE’s cache and are much faster.

Why learn this

It’s the shortest path from “I have a sensor” to “I have a classifier running on the board.” In the classroom, students collect their own data and immediately see the effect of poor data or imbalanced classes—the most important lesson in any AI project.

Key concepts

Impulse
window + blocks
Label
the correct answer
Data forwarder
serial → Edge Impulse
Confusion matrix
which classes get confused with which
4

⚙️ TensorFlow Lite Micro / LiteRT on the ESP32-S3

Behind many TinyML projects is TensorFlow Lite Micro (TFLM), Google’s inference engine for microcontrollers. In 2024, TensorFlow Lite was renamed LiteRT; you’ll see both names. Espressif maintains the esp-tflite-micro component for ESP-IDF, with ready-to-use examples (hello_world, micro_speech, person_detection) and ESP-NN, which uses the S3’s vector instructions to speed up network layers.

.tflite modelC array in flash op resolverlayers used interpreterInvoke()arena in RAM int8 input output: scores ESP-NN (S3)

How to read this map: on the left is what you prepare once (the model and the list of layers). In the center, the interpreter performs calculations in a fixed area of RAM. On the right is the cycle for each inference: write the input, call Invoke(), read the output. The dashed line below indicates ESP-NN acceleration, which kicks in automatically on the S3.

The five steps in every TFLM program

1

tflite::GetModel(g_model) loads the model converted to a C array (with xxd -i).

2

MicroMutableOpResolver registers only the layers used (convolution, fully connected, softmax), saving flash.

3

MicroInterpreter takes the model, resolver, and arena; AllocateTensors() reports if the arena is too small.

4

You fill interpreter.input(0) with data already converted to int8, then call Invoke().

5

Read interpreter.output(0): one score per class. The highest score is the answer.

Goal: compile and flash the official example hello_world (a network that learns the sine curve) on the ESP32-S3. Requires ESP-IDF 5.x to be installed and the ESP-IDF terminal to be open.

terminal (ESP-IDF)
# 1. baixar o componente com os exemplos
git clone https://github.com/espressif/esp-tflite-micro.git
cd esp-tflite-micro/examples/hello_world

# 2. escolher o chip e compilar
idf.py set-target esp32s3
idf.py build

# 3. gravar e abrir o monitor (troque a porta pela sua)
idf.py -p /dev/ttyACM0 flash monitor
# no Windows a porta é algo como COM5; para sair do monitor: Ctrl+]

How to check

  • ✓The monitor shows lines with an x value and the predicted y, rising and falling like a sine wave.
  • ✓Did it work? Repeat with examples/person_detection; it requires a board with a camera.
  • ✗"idf.py: command not found": the terminal has not loaded the ESP-IDF environment. Open the "ESP-IDF Terminal" or run the ESP-IDF export script.

Why learn this

Edge Impulse handles most cases, but when you train your own network with TensorFlow or want to control every byte of memory, TFLM is the tool to use. Understanding how the interpreter works also helps explain error messages from any TinyML library, including Edge Impulse.

Key concepts

LiteRT
new name for TF Lite
esp-tflite-micro
ESP-IDF component
Invoke()
runs an inference
ESP-NN
acceleration on the S3
5

👋 Example: recognize a gesture with the IMU

Use the MPU-6050 from module 2.2 connected to the ESP32-S3 and teach it three classes: stopped, shake e circle. The model does not look at a single reading, but at a window: for example, 2 seconds of acceleration on all three axes, read 100 times per second. You need two sketches: one to collect data and another to classify it.

axayaz window: 2 s × 100 Hz = 200 × 3 scores still 0.03 shake 0.91 circle 0.06 the window moves forward in time and the classifier runs again

How to read this map: The three lines are the accelerometer axes. The rectangle is the window the model sees all at once. On the right is the output: a score from 0 to 1 for each class, adding up to 1. Accept the result only if the highest score exceeds a threshold (0.8 in the sketch).

Goal (data collection): print the acceleration in m/s², 100 times per second, in the format that edge-impulse-data-forwarder understands (comma-separated values, one line per reading).

coleta-imu.ino
// Collect data from the MPU-6050 for Edge Impulse (ESP32-S3, esp32 3.x package)
// Output: "ax,ay,az" in m/s², 100 lines per second, 115200 baud.
#include <Wire.h>

const int PINO_SDA = 8;           // replace with your board's I2C pins
const int PINO_SCL = 9;
const uint8_t MPU = 0x68;        // MPU-6050 address (AD0 to GND)
const unsigned long PERIODO_US = 10000; // 10 ms = 100 Hz
unsigned long proxima = 0;

void escreveRegistro(uint8_t reg, uint8_t valor) {
  Wire.beginTransmission(MPU); Wire.write(reg); Wire.write(valor); Wire.endTransmission();
}

void setup() {
  Serial.begin(115200);
  Wire.begin(PINO_SDA, PINO_SCL);
  Wire.setClock(400000);
  escreveRegistro(0x6B, 0x00);      // wake up the sensor
  escreveRegistro(0x1C, 0x00);      // ±2 g range: 16384 per g
  proxima = micros();
}

void loop() {
  if ((long)(micros() - proxima) < 0) return;  // wait for the next 10 ms tick
  proxima += PERIODO_US;
  Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false);
  Wire.requestFrom(MPU, (uint8_t)6);
  if (Wire.available() < 6) return;
  int16_t bruto[3];
  for (int i = 0; i < 3; i++) { uint8_t alto = Wire.read(), baixo = Wire.read(); bruto[i] = (int16_t)((alto << 8) | baixo); }
  const float G = 9.80665f / 16384.0f;  // counts → m/s²
  Serial.printf("%.3f,%.3f,%.3f\n", bruto[0] * G, bruto[1] * G, bruto[2] * G);
}

How to check

  • ✓Sitting on the table, one axis reads about 9.8 and the others close to 0.
  • ✓Close the Serial Monitor and run edge-impulse-data-forwarder: it detects about 100 Hz and 3 axes. Record at least 3 minutes per class.
  • ✗All zeros? Check SDA/SCL and whether the sensor responds at address 0x68.

Goal (classification): with the exported library installed, fill the window with readings at the same rate used for training, call the classifier, and turn on the LED when the "shake" gesture scores above 0.8. Replace the name of #include with your project's header and the labels with the ones you created.

gesto-imu.ino
// Classify gestures with the library exported by Edge Impulse (ESP32-S3)
#include <Wire.h>
#include <seu_projeto_inferencing.h>  // replace with the generated header: <seu_projeto>_inferencing.h

const int PINO_SDA = 8, PINO_SCL = 9, PINO_LED = 2;
const uint8_t MPU = 0x68;
const float LIMITE = 0.80f;
static float janela[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];  // 200 readings × 3 axes

void escreveRegistro(uint8_t reg, uint8_t valor) {
  Wire.beginTransmission(MPU); Wire.write(reg); Wire.write(valor); Wire.endTransmission();
}

void leAceleracao(float *a) {
  Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false);
  Wire.requestFrom(MPU, (uint8_t)6);
  const float G = 9.80665f / 16384.0f;
  for (int i = 0; i < 3; i++) { uint8_t alto = Wire.read(), baixo = Wire.read(); a[i] = (int16_t)((alto << 8) | baixo) * G; }
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_LED, OUTPUT);
  Wire.begin(PINO_SDA, PINO_SCL); Wire.setClock(400000);
  escreveRegistro(0x6B, 0x00); escreveRegistro(0x1C, 0x00);
  Serial.printf("Model ready: %d classes, window of %d values\n", EI_CLASSIFIER_LABEL_COUNT, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
}

void loop() {
  // 1) fill the window at the same rate used for training
  for (size_t i = 0; i < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; i += EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
    unsigned long t0 = micros();
    leAceleracao(&janela[i]);
    while (micros() - t0 < (unsigned long)(EI_CLASSIFIER_INTERVAL_MS * 1000)) {}
  }
  // 2) run the classifier
  signal_t sinal;
  numpy::signal_from_buffer(janela, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &sinal);
  ei_impulse_result_t resultado = { 0 };
  if (run_classifier(&sinal, &resultado, false) != EI_IMPULSE_OK) { Serial.println("classifier error"); return; }
  // 3) show the scores and decide
  bool chacoalhou = false;
  for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
    Serial.printf("%s: %.2f  ", resultado.classification[i].label, resultado.classification[i].value);
    if (strcmp(resultado.classification[i].label, "chacoalhar") == 0 && resultado.classification[i].value > LIMITE) chacoalhou = true;
  }
  Serial.printf("| %d ms\n", resultado.timing.dsp + resultado.timing.classification);
  digitalWrite(PINO_LED, chacoalhou ? HIGH : LOW);
}

How to check

  • ✓Every 2 seconds, a line appears with the three scores and the inference time in milliseconds.
  • ✓Shake the board: the "shake" score goes above 0.8 and the LED turns on.
  • ✗Compilation error in #include? The header name does not match the installed library.

Why learn this

The same approach works for detecting when the robot has tipped over, when a wheel is wobbling, or when someone has picked up the robot. These reactions need to happen locally and quickly, without waiting for the Pi.

Key concepts

Window
time segment being analyzed
Fixed frequency
same during training and on the board
run_classifier
calls the model
Threshold
accepts only a high score
6

🎙️ Example: voice keyword or person detection with the camera

Boards such as the XIAO ESP32-S3 Sense come with a microphone and camera. With them, you can run the two classic esp-tflite-micro examples: the micro_speech, which recognizes the English words "yes" and "no", and the person_detection, which gives an "is there a person" score for a 96×96-pixel grayscale image. For Portuguese words, train your keyword model in Edge Impulse using recordings from your class.

VOICE yes · nosilence · other 1 s of audiospectrogram CAMERA QVGA frame 96×96 grayscale person 0.87no person 0.13

How to read this map: in both workflows, the microcontroller first shrinks the input (sound becomes a frequency map; the image becomes 96×96 grayscale) and only then calls the model. This preprocessing is half the work and must match what was used during training.

✓ Right expectations

  • ✓A few short words that are clearly different from one another ("forward", "stop").
  • ✓"Is there a person?" from one or two meters away, in good light.
  • ✓Record voices from several people with the motor noise on.

✗ Wrong expectations

  • ✗Transcribing sentences: that is Whisper, on the Pi or PC (Track 5).
  • ✗Saying where the person is and who they are: the model only answers yes or no.
  • ✗Training only with your voice and expecting it to work for the whole class.

Goal: flash the official person detector to an ESP32-S3 board with a camera and PSRAM. Each board’s camera pins are listed in the example’s configuration file; check its README to see if your board is already listed.

terminal (ESP-IDF)
cd esp-tflite-micro/examples/person_detection
idf.py set-target esp32s3
idf.py menuconfig      # check the board/camera and whether PSRAM is enabled
idf.py build
idf.py -p /dev/ttyACM0 flash monitor

How to check

  • ✓The monitor prints the scores for "person" and "no person" with each image; point the camera at yourself and the first score goes up.
  • ✗Camera failed to start? The configured pins don’t match your board, or PSRAM is turned off.

🌱 New here?

A spectrogram It’s a “picture” of sound: the horizontal axis is time, the vertical axis is frequency, and the color shows intensity. Keyword models treat this map as if it were a small image. Keyword (keyword spotting) means recognizing a word from a short list, not understanding speech.

Why learn this

A spoken “stop” that the ESP32 understands on its own is a safety lock that doesn’t depend on Wi-Fi or the Pi. And an inexpensive person detector can wake the Pi only when someone is in front of it, saving battery. In module 3.5, you’ll connect these two worlds with a serial wire.

Key concepts

Keyword
short list of words
Spectrogram
sound as an image
person_detection
person: yes or no
Preprocessing
same as during training

✅ Module summary

✓
AI next to the sensor: less delay, less power, and nothing leaves the robot; the tradeoff is a small model.
✓
int8 is the standard: quantization makes the model 4 times smaller and faster.
✓
Edge Impulse: collect, label, train, and export as an Arduino library.
✓
TFLM / LiteRT: model, resolver, interpreter, arena, and Invoke(); esp-tflite-micro on the S3.
✓
Set the right expectations: gestures, a few words, and “is there a person?” are within reach; full object detection stays on the Pi.

To go further

Next module

3.5 · Raspberry Pi talks to the ESP32: the Pi decides, the ESP32 acts, connected by a serial wire with a protocol and a safe stop.