TinyML: Development Environment

Get the full pipeline running before writing any model code. A broken toolchain wastes hours at the worst possible moment.

What You Need

Desktop side:
  Python 3.9+
  TensorFlow 2.13+
  numpy, pandas, matplotlib
  tflite-model-maker (optional, simplifies training)
  arduino-cli or Arduino IDE 2.x

Device side:
  Arduino Nano 33 BLE Sense Rev2 (or ESP32-S3 dev board)
  USB cable (the Nano uses micro-USB; confirm before ordering)

Python Environment

Use a virtual environment. TensorFlow has strict version requirements and pollutes global packages.

python3 -m venv tinyml-env
source tinyml-env/bin/activate         # macOS/Linux
# tinyml-env\Scripts\activate.bat      # Windows

pip install --upgrade pip
pip install tensorflow==2.13.0 numpy pandas matplotlib scikit-learn

Verify TensorFlow sees your hardware:

import tensorflow as tf
print(tf.__version__)          # 2.13.0
print(tf.config.list_physical_devices())

On Apple Silicon (M1/M2/M3), use tensorflow-metal instead for GPU acceleration:

pip install tensorflow-metal

Checking TFLite Converter

The converter ships with TensorFlow. Confirm it works:

import tensorflow as tf

# Build a trivial model
model = tf.keras.Sequential([
    tf.keras.layers.Dense(4, activation='relu', input_shape=(2,)),
    tf.keras.layers.Dense(1, activation='sigmoid'),
])
model.compile(optimizer='adam', loss='binary_crossentropy')

# Convert
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
print(f"Model size: {len(tflite_model)} bytes")  # ~500 bytes for this toy model

If this runs without error, the Python side is ready.

Arduino CLI Setup

The Arduino IDE works too, but arduino-cli is easier to script and pairs better with Python workflows.

# macOS (Homebrew)
brew install arduino-cli

# Linux
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
# add ~/.arduino15 to PATH or move binary to /usr/local/bin

# Verify
arduino-cli version

Initialize config and install the board core:

arduino-cli config init
arduino-cli core update-index

# Nano 33 BLE Sense (nRF52840, mbed core)
arduino-cli core install arduino:mbed_nano

# ESP32-S3
arduino-cli core install esp32:esp32 --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Install the TFLite Micro library:

arduino-cli lib install "Arduino_TensorFlowLite"

# Additional libraries for Nano 33 BLE Sense sensors
arduino-cli lib install "Arduino_LSM9DS1"     # older Rev1 IMU
arduino-cli lib install "Arduino_LSM6DSOX"    # Rev2 IMU
arduino-cli lib install "Arduino_PDM"         # microphone

Verifying the Board Connection

# List connected boards
arduino-cli board list

# Example output:
# Port         Protocol  Type              Board Name           FQBN
# /dev/ttyACM0 serial    Serial Port (USB) Arduino Nano 33 BLE  arduino:mbed_nano:nano33ble

If the board doesn't appear, check the USB cable (data-capable, not charge-only) and the driver. On Linux, add your user to the dialout group:

sudo usermod -a -G dialout $USER
# log out and back in for the change to take effect

Compiling and Flashing a Sketch

Create a minimal sketch to confirm the board accepts firmware:

// blink.ino
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
}
# Compile
arduino-cli compile --fqbn arduino:mbed_nano:nano33ble blink/

# Upload (adjust port)
arduino-cli upload --fqbn arduino:mbed_nano:nano33ble -p /dev/ttyACM0 blink/

# Monitor (Ctrl+C to quit)
arduino-cli monitor -p /dev/ttyACM0 --config baudrate=9600

The built-in LED should blink at 1 Hz. If it does, the toolchain is working.

Project Directory Structure

Organize your work like this from the start:

gesture-project/
├── data/
│   ├── raw/           # CSV files captured from the device
│   └── processed/     # Numpy arrays ready for training
├── training/
│   ├── train.py       # Model definition and training script
│   ├── convert.py     # Quantization and .tflite export
│   └── model.tflite   # Exported model
├── firmware/
│   └── gesture_infer/
│       ├── gesture_infer.ino
│       ├── model_data.h   # generated from model.tflite
│       └── model_data.cc  # generated from model.tflite
└── README.md

Keeping training/ and firmware/ separate prevents the Arduino compiler from trying to compile Python files and makes the pipeline obvious.

Generating the C Array

Once you have a .tflite file, convert it to a C array the Arduino sketch can include:

xxd -i model.tflite > model_data.cc

xxd is standard on macOS and most Linux distributions. On Windows, use the Git Bash shell.

The output looks like this:

// model_data.cc  (generated; do not edit by hand)
unsigned char model_tflite[] = {
  0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, ...
};
unsigned int model_tflite_len = 4216;

Rename model_tflite to something readable (e.g., g_gesture_model_data) in both the .cc file and the corresponding .h header:

// model_data.h
#pragma once
extern const unsigned char g_gesture_model_data[];
extern const unsigned int  g_gesture_model_data_len;

Common Setup Issues

TensorFlow install hangs on Apple Silicon: use pip install tensorflow-macos tensorflow-metal instead of plain tensorflow.

Board not found after install: run arduino-cli core update-index and retry. Sometimes the index fetch is stale.

xxd not found on Windows: install Git for Windows, which includes a Unix tools layer. Alternatively, Python can do the conversion:

with open("model.tflite", "rb") as f:
    data = f.read()
values = ", ".join(f"0x{b:02x}" for b in data)
with open("model_data.cc", "w") as f:
    f.write(f"unsigned char g_model_data[] = {{{values}}};\n")
    f.write(f"unsigned int g_model_data_len = {len(data)};\n")

Compilation errors about missing ops: AllOpsResolver includes all operators and avoids this. Use it during development; switch to MicroMutableOpResolver when optimizing binary size (chapter 11).

Next Steps

Continue to 04-data-collection.md to capture sensor data directly from the device and build a labeled dataset for training.