Building Your First Project: A Smart Motion Tracker¶
Ready to put everything together? You've mastered the basics – flashed some LEDs, read IMU data, and hopefully enjoyed those gummy bears. Now it's time to build your first complete IoT device that measures, records, and wirelessly transmits motion data.
By the end of this tutorial, you'll have a working motion tracker that can log data locally on the SD Card, and stream it live to your computer for real-time visualization. With this, you can build all sorts of awesome movement-measuring projects (e.g fitness trackers, gesture-based controllers, or physics lab equipment).
Before You Start
Make sure you've completed the previous tutorials:
You'll also need your micro SD card from the kit!
What We're Building¶
This motion tracker combines everything special about the tinyCore into one smart device: Motion Sensing, Local Data Storage, Wireless Data Streaming
Normally you'd need to buy and wire together multiple breakout boards, but we've got a tinyCore so everything we need is included.
Real-World Applications¶
Students have used this exact setup to create:
-
Fitness rep counters and form analyzers
-
Musical gesture controllers
-
Physics lab acceleration experiments
-
Door entry monitoring systems
-
Even a "FitBit for cows" (seriously!)
System Architecture: How It All Fits Together¶
Part 1: The Code¶
Our motion tracker follows this software flow:
flowchart TD
START([Power On]) --> INIT([Initialize Sensors])
INIT --> SETUP([Setup WiFi & SD Card])
SETUP --> LOOP{ Loop }
LOOP --> READ[Read IMU Data]
READ --> PROCESS[Apply Filters]
PROCESS --> LOG[Log to SD Card]
LOG --> STREAM[Stream via WiFi]
STREAM --> LOOP
The Complete Motion Tracker Code¶
Copy this code into your Arduino IDE:
#include <Adafruit_LSM6DSOX.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <SD.h>
#include <SPI.h>
// WiFi credentials - Change these!
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
const char* host_ip = "192.168.1.100"; // Your computer's IP
const int udp_port = 12345;
// Hardware setup
Adafruit_LSM6DSOX lsm6dsox;
WiFiUDP udp;
File dataFile;
// Timing and data
unsigned long lastSample = 0;
const unsigned long SAMPLE_INTERVAL = 50; // 20Hz sampling
unsigned long sessionStart;
int packetCount = 0;
// Simple moving average filter
struct MotionData {
float accelX, accelY, accelZ;
float gyroX, gyroY, gyroZ;
float temp;
unsigned long timestamp;
};
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("=== tinyCore Motion Tracker Starting ===");
// Initialize IMU
setupIMU();
// Initialize SD Card
setupSDCard();
// Connect to WiFi
setupWiFi();
sessionStart = millis();
Serial.println("Motion tracker ready! Starting data collection...");
}
void setupIMU() {
// IMU power and I2C setup
pinMode(6, OUTPUT);
digitalWrite(6, HIGH);
Wire.begin(3, 4);
delay(100);
if (!lsm6dsox.begin_I2C()) {
Serial.println("ERROR: IMU not found!");
while(1) delay(10);
}
// Configure for motion tracking
lsm6dsox.setAccelRange(LSM6DS_ACCEL_RANGE_4_G); // ±4g range
lsm6dsox.setGyroRange(LSM6DS_GYRO_RANGE_500_DPS); // ±500 degrees/sec
lsm6dsox.setAccelDataRate(LSM6DS_RATE_104_HZ);
lsm6dsox.setGyroDataRate(LSM6DS_RATE_104_HZ);
Serial.println("✓ IMU initialized");
}
void setupSDCard() {
if (!SD.begin()) {
Serial.println("WARNING: SD Card not found - logging disabled");
return;
}
// Create new session file
String filename = "/motion_" + String(millis()) + ".csv";
dataFile = SD.open(filename, FILE_WRITE);
if (dataFile) {
dataFile.println("timestamp,accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z,temp");
dataFile.flush();
Serial.println("✓ SD Card ready - " + filename);
}
}
void setupWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.println("✓ WiFi connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
udp.begin(udp_port);
} else {
Serial.println();
Serial.println("WARNING: WiFi connection failed - streaming disabled");
}
}
void loop() {
unsigned long currentTime = millis();
if (currentTime - lastSample >= SAMPLE_INTERVAL) {
MotionData data = readMotionData();
// Log to SD card
logToSD(data);
// Stream via WiFi
streamData(data);
lastSample = currentTime;
packetCount++;
// Status update every 5 seconds
if (packetCount % 100 == 0) {
Serial.println("Packets sent: " + String(packetCount));
}
}
}
MotionData readMotionData() {
sensors_event_t accel, gyro, temp;
lsm6dsox.getEvent(&accel, &gyro, &temp);
MotionData data;
data.accelX = accel.acceleration.x;
data.accelY = accel.acceleration.y;
data.accelZ = accel.acceleration.z;
data.gyroX = gyro.gyro.x;
data.gyroY = gyro.gyro.y;
data.gyroZ = gyro.gyro.z;
data.temp = temp.temperature;
data.timestamp = millis() - sessionStart;
return data;
}
void logToSD(MotionData data) {
if (!dataFile) return;
dataFile.print(data.timestamp); dataFile.print(",");
dataFile.print(data.accelX, 3); dataFile.print(",");
dataFile.print(data.accelY, 3); dataFile.print(",");
dataFile.print(data.accelZ, 3); dataFile.print(",");
dataFile.print(data.gyroX, 3); dataFile.print(",");
dataFile.print(data.gyroY, 3); dataFile.print(",");
dataFile.print(data.gyroZ, 3); dataFile.print(",");
dataFile.println(data.temp, 1);
// Flush every 10 samples to prevent data loss
if (packetCount % 10 == 0) {
dataFile.flush();
}
}
void streamData(MotionData data) {
if (WiFi.status() != WL_CONNECTED) return;
// Create JSON packet for easy parsing
String packet = "{";
packet += "\"t\":" + String(data.timestamp) + ",";
packet += "\"ax\":" + String(data.accelX, 3) + ",";
packet += "\"ay\":" + String(data.accelY, 3) + ",";
packet += "\"az\":" + String(data.accelZ, 3) + ",";
packet += "\"gx\":" + String(data.gyroX, 3) + ",";
packet += "\"gy\":" + String(data.gyroY, 3) + ",";
packet += "\"gz\":" + String(data.gyroZ, 3) + ",";
packet += "\"temp\":" + String(data.temp, 1);
packet += "}";
udp.beginPacket(host_ip, udp_port);
udp.print(packet);
udp.endPacket();
}
Code Walkthrough¶
Setup Phase: - Initialize IMU with optimal settings for motion tracking - Prepare SD card with timestamped CSV file - Connect to your WiFi network
Main Loop: - Sample IMU data at 20Hz (every 50ms) - Apply simple filtering for noise reduction - Log structured data to SD card - Stream JSON packets via UDP for real-time visualization
Part 2: Upload and Test¶
1. Configure Your Network¶
Before uploading, update these lines in the code:
const char* ssid = "YOUR_WIFI_NAME"; // Your WiFi network name
const char* password = "YOUR_WIFI_PASSWORD"; // Your WiFi password
const char* host_ip = "192.168.1.100"; // Your computer's IP address
Finding Your Computer's IP
Windows: Open Command Prompt, type ipconfig
Mac/Linux: Open Terminal, type ifconfig
Look for your local IP address (usually starts with 192.168.x.x)
2. Insert SD Card and Upload¶
- Insert the micro SD card from your kit into the tinyCore
- Connect via USB-C and select your board/port
- Upload the code and open the Serial Monitor
- Watch for startup messages - you should see:
3. Test Motion Detection¶
Start moving your tinyCore around! You should see packet count updates in the Serial Monitor every 5 seconds, confirming data is being collected and transmitted.
Part 3: Python Visualization App¶
Create this Python script to see your motion data in real-time:
import socket
import json
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from collections import deque
import numpy as np
# Network settings (match your Arduino code)
UDP_IP = "0.0.0.0" # Listen on all interfaces
UDP_PORT = 12345
# Data storage
max_points = 500
timestamps = deque(maxlen=max_points)
accel_data = {'x': deque(maxlen=max_points),
'y': deque(maxlen=max_points),
'z': deque(maxlen=max_points)}
gyro_data = {'x': deque(maxlen=max_points),
'y': deque(maxlen=max_points),
'z': deque(maxlen=max_points)}
# Setup UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
sock.settimeout(0.1) # Non-blocking
# Setup plots
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
fig.suptitle('tinyCore Motion Tracker - Live Data', fontsize=16)
def update_plot(frame):
# Try to receive data
try:
data, addr = sock.recvfrom(1024)
packet = json.loads(data.decode())
# Store data
timestamps.append(packet['t'] / 1000.0) # Convert to seconds
accel_data['x'].append(packet['ax'])
accel_data['y'].append(packet['ay'])
accel_data['z'].append(packet['az'])
gyro_data['x'].append(packet['gx'])
gyro_data['y'].append(packet['gy'])
gyro_data['z'].append(packet['gz'])
except (socket.timeout, json.JSONDecodeError):
pass # No data received, continue with existing data
if len(timestamps) < 2:
return
# Clear and redraw plots
ax1.clear()
ax2.clear()
time_array = list(timestamps)
# Plot acceleration
ax1.plot(time_array, list(accel_data['x']), 'r-', label='X-axis', linewidth=2)
ax1.plot(time_array, list(accel_data['y']), 'g-', label='Y-axis', linewidth=2)
ax1.plot(time_array, list(accel_data['z']), 'b-', label='Z-axis', linewidth=2)
ax1.set_title('Acceleration (m/s²)')
ax1.set_ylabel('Acceleration')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Plot gyroscope
ax2.plot(time_array, list(gyro_data['x']), 'r-', label='X-rotation', linewidth=2)
ax2.plot(time_array, list(gyro_data['y']), 'g-', label='Y-rotation', linewidth=2)
ax2.plot(time_array, list(gyro_data['z']), 'b-', label='Z-rotation', linewidth=2)
ax2.set_title('Gyroscope (rad/s)')
ax2.set_xlabel('Time (seconds)')
ax2.set_ylabel('Angular Velocity')
ax2.legend()
ax2.grid(True, alpha=0.3)
# Start animation
print(f"Listening for motion data on port {UDP_PORT}...")
print("Start your tinyCore motion tracker!")
ani = animation.FuncAnimation(fig, update_plot, interval=50, blit=False)
plt.tight_layout()
plt.show()
Running the Visualizer¶
-
Install required Python packages:
-
Run the script:
-
Start moving your tinyCore - you should see real-time graphs!
Part 4: Testing Your Motion Tracker¶
Experiment Ideas¶
Try these movements and watch the data patterns:
1. Gravity Test - Place tinyCore flat on table - Z-axis acceleration should read ~9.8 m/s² (gravity!) - Other axes should be near zero
2. Shake Test - Shake the device back and forth - Watch acceleration spikes in all directions - Gyroscope should show rotational movement
3. Circle Drawing - Draw smooth circles in the air - Look for sine wave patterns in the data - Try different orientations and speeds
4. Drop Test (Carefully!) - Drop the tinyCore a few inches onto a soft surface - You should see a brief period of zero acceleration (free fall!)
Data Analysis¶
Your SD card now contains CSV files with all your motion data! You can: - Import into Excel/Google Sheets for analysis - Use Python pandas for advanced processing - Calculate total movement, detect patterns, or train ML models
Understanding the Results¶
Reading the Graphs¶
Acceleration Data: - Steady values around ±9.8: Device orientation relative to gravity - Sharp spikes: Quick movements or impacts - Smooth curves: Rotational movements
Gyroscope Data: - Values near zero: No rotation - Positive/negative peaks: Rotation direction and speed - Smooth curves: Consistent spinning motion
Troubleshooting¶
No WiFi connection? - Double-check your network credentials - Make sure you're on a 2.4GHz network (ESP32 doesn't support 5GHz) - Try moving closer to your router
SD card not working? - Ensure the card is properly inserted - Try reformatting as FAT32 - Check that the card isn't write-protected
Python script not receiving data? - Verify IP addresses match between Arduino code and your computer - Check that both devices are on the same network - Make sure no firewall is blocking UDP traffic
What's Next?¶
You've built a complete IoT motion sensing system! Here are some ways to expand it:
Hardware Additions: - Add external sensors via the QWIIC connectors - Connect LEDs or buzzers for motion-triggered alerts - Attach a battery for portable operation
Software Enhancements:
- Implement Kalman filtering for smoother data
- Add gesture recognition algorithms
- Create a web dashboard for remote monitoring
- Set up cloud data logging
Project Ideas: - Fitness Tracker: Count reps, detect exercise types - Security System: Motion-triggered alerts - Musical Interface: Control sound with gestures - Physics Experiments: Measure pendulum motion, free fall - IoT Sensor Network: Multiple tinyCore devices reporting to central hub
Wrap Up¶
In about 30 minutes, you've built a professional-grade motion tracking system that combines: - Real-time sensor data acquisition - Local data logging with timestamps - Wireless data transmission - Computer-based visualization - Expandable architecture for future projects
This is the foundation for countless IoT applications. The integrated design of the tinyCore made this possible without breadboard wiring or complex setup – just code, upload, and go!
Now get creative and see what motion-based projects you can dream up. The world needs more makers who can turn ideas into working prototypes this quickly.
Achievement Unlocked!
IoT Motion Tracker Complete! 🎉
You've successfully built and programmed a complete wireless motion sensing system. Time to show it off!
Need Help?
Join our Discord or email support@mr.industries – we love seeing what you build!