Learning Goals 5 min
Before you can train a model, you need labelled data. Today you stream accelerometer readings from the Nano 33 BLE Sense to your laptop, record N seconds of each gesture, and save labelled CSV files ready for tomorrow's training. By the end of this lesson you will:
- Write an Arduino sketch that streams IMU readings over Serial in CSV format.
- Use a laptop-side script (Python or a serial recorder app) to capture into files named by gesture label.
- Collect ~30 samples each of three gestures, balanced and varied.
Warm-Up 10 min
Hardware:
- Nano 33 BLE Sense (or any board with an MPU6050).
- USB cable to laptop.
- Python 3 on laptop, or Edge Impulse Studio (browser-based — easier).
Choose your three gestures
For the magic wand we'll build in L04-36: pick something distinct. Suggested:
- "wave" — back-and-forth swing.
- "punch" — quick straight thrust.
- "circle" — round motion.
New Concept · Streaming + recording 25 min
The capture sketch
// Streams IMU readings while a button is held.
// On press: print "BEGIN <label>" + 1.5 s of samples + "END".
#include <Arduino_LSM9DS1.h> // Nano 33 BLE Sense IMU
const int BTN_PIN = 2;
const int SAMPLE_HZ = 100; // 100 Hz
const int CAPTURE_MS = 1500; // 1.5 s
const char* LABEL = "wave"; // set this per session
bool wasButton = false;
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP);
while (!Serial);
if (!IMU.begin()) {
Serial.println("# IMU init failed");
while (true);
}
Serial.println("# Ready. Press button to capture a gesture.");
}
void loop() {
bool pressed = digitalRead(BTN_PIN) == LOW;
if (pressed && !wasButton) {
Serial.print("BEGIN "); Serial.println(LABEL);
unsigned long start = millis();
while (millis() - start < CAPTURE_MS) {
float ax, ay, az;
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(ax, ay, az);
Serial.print(ax); Serial.print(",");
Serial.print(ay); Serial.print(",");
Serial.println(az);
}
delay(1000 / SAMPLE_HZ);
}
Serial.println("END");
}
wasButton = pressed;
}The serial output for one gesture looks like:
BEGIN wave 0.12,0.05,9.78 0.20,0.10,9.75 ... 0.15,0.08,9.79 END
Recording to files (Python)
# capture.py (laptop)
import serial, sys
ser = serial.Serial("/dev/tty.usbmodem142101", 115200) # update port
label = sys.argv[1] if len(sys.argv) > 1 else "unknown"
trial = 0
while True:
line = ser.readline().decode().strip()
if line.startswith("BEGIN"):
trial += 1
filename = f"{label}_{trial:03d}.csv"
f = open(filename, "w")
f.write("ax,ay,az\n")
elif line == "END":
f.close()
print(f"saved {filename}")
elif line and not line.startswith("#"):
f.write(line + "\n")
Run: python capture.py wave. Press the button on the Nano 30 times, performing the gesture each time. You'll get wave_001.csv, wave_002.csv, ..., wave_030.csv.
Then change LABEL in the sketch (or pass via Serial), re-upload, and capture "punch_001..030" and "circle_001..030".
Quicker: Edge Impulse
Edge Impulse Studio (free for hobbyists) is a web app that handles the whole pipeline: capture from your Nano over USB, label, train, deploy. Use this if you want a polished workflow without writing Python. We'll use the manual workflow here to understand what's happening.
Worked Example · Record 30 of each 25 min
Step 1 — capture wave samples
- Set LABEL = "wave". Upload.
- Run capture.py with "wave" on the laptop.
- Hold the Nano. Press the button. Wave.
- Repeat 30 times with small variations.
Step 2 — punch samples
Update LABEL to "punch". Re-upload. Run capture.py with "punch". 30 punches.
Step 3 — circle samples
Same flow. 30 circles.
Step 4 — verify in Python
import pandas as pd
import glob, matplotlib.pyplot as plt
for f in glob.glob("wave_001.csv")[:1]:
df = pd.read_csv(f)
df.plot()
plt.savefig(f.replace(".csv", ".png"))Look at the plots. Wave should have an oscillating pattern; punch a single sharp peak; circle a smooth rotation. If they all look the same, your labels are wrong — re-capture.
Step 5 — augmentation (optional)
Take each captured gesture and shift it by ±50 ms, add small Gaussian noise, scale slightly. Generates 5× more training data from the same 30 real captures. Helps generalisation.
Step 6 — bundle for training
Tomorrow we'll feed all 90 CSVs into a neural network. Make sure files are organised in folders: data/wave/, data/punch/, data/circle/.
Basic 5 min
Goal: Add gyro readings (3 more channels) to each capture. More features → potentially more accuracy.
Challenge 1 5 min
Goal: Have a classmate capture 30 of each gesture using YOUR Nano. Now you have 60 of each, from 2 people. Compare model performance with combined vs single-person training.
Challenge 2 5 min
Goal: Fix a broken capture sketch. The button is wired from D2 to GND. A classmate's capture files are useless. Captures start on their own and repeat while the button is held. They are far too short, and capture.py saves one number per line. There are four mistakes.
#include <Arduino_LSM9DS1.h>
const int BTN_PIN = 2;
const int SAMPLE_HZ = 100;
const int CAPTURE_MS = 150;
const char* LABEL = "wave";
bool wasButton = false;
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT);
while (!Serial);
if (!IMU.begin()) {
Serial.println("# IMU init failed");
while (true);
}
Serial.println("# Ready. Press button to capture a gesture.");
}
void loop() {
bool pressed = digitalRead(BTN_PIN) == LOW;
if (pressed && !wasButton) {
Serial.print("BEGIN ");
Serial.println(LABEL);
unsigned long start = millis();
while (millis() - start < CAPTURE_MS) {
float ax, ay, az;
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(ax, ay, az);
Serial.println(ax);
Serial.print(",");
Serial.print(ay);
Serial.print(",");
Serial.println(az);
}
delay(1000 / SAMPLE_HZ);
}
Serial.println("END");
}
}Match each symptom to its mistake, then fix it. Work out how many sample lines one 1.5-second capture should give.
It works if one press gives one BEGIN wave, about 150 lines of three comma-separated numbers, and one END.
Challenge 3 · 90 clean samples 10 min
- 30 × wave, 30 × punch, 30 × circle.
- All labelled, all readable as CSV.
- Plot one of each — confirm the shapes are different.
- Save as a zip / folder for tomorrow.
Recap 5 min
Training data = many labelled examples. Capture 1.5 s windows of accelerometer at 100 Hz. 30+ examples per gesture, varied across speed / angle / user. CSV per capture; folder per label. Tomorrow we feed this into TensorFlow.
- Window length
- Duration of one captured example. Should be slightly longer than the longest gesture you want to detect.
- Sample rate
- How many readings per second. 100 Hz is plenty for hand gestures.
- Data augmentation
- Synthetically expanding the dataset by transforming examples (noise, time-shift, scale). Cheaper than collecting more.
- Label folder structure
- Common convention:
data/<label>/<sample>.csv. Many ML libraries auto-load this. - Edge Impulse
- Browser-based no-code pipeline for capturing, training, deploying edge ML. Free for hobby use.
- CSV (Comma-Separated Values)
- Simplest data format. One row per sample, columns for each channel.
Extra Mission 5 min
Part 1 — Design your own gesture set
Gestures only help if they are easy to do and easy to tell apart. On paper, design three gestures to control one gadget. It might be a music player (next, back, pause) or a slideshow (forward, back, blank screen).
Your design must include:
- The gadget and the job each gesture does.
- A drawing of each movement, with arrows.
- Why the three will look different in the accelerometer data.
- Your capture plan: samples per gesture, window length, and the variation you will add.
Part 2 — Make it
Capture your own three gestures with today's capture sketch and capture.py. Change LABEL for each one. Collect at least 30 clean samples of each. Plot one of each and check the shapes differ.
Bring back next class: your folder of labelled CSV files and a screenshot of the three plots side by side. You will train on them in ARD-L04-34.