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.