How to Build a Fitness Wristband Prototype with ESP32
Heart-rate sampling, step estimation, and low-power OLED display
Updated

What you'll build
This guide takes you through building a fitness wristband prototype using an ESP32, a MAX30102 pulse oximetry and heart-rate sensor, an MPU6050 accelerometer and gyroscope, and a 0.96-inch OLED display. The wristband samples heart-rate data by shining red and infrared LEDs through your fingertip or wrist, detects steps using a peak-detection algorithm on the accelerometer's Z-axis, and presents both metrics on a crisp OLED screen that cycles between a live heart-rate graph, a daily step count, and a simple clock face. A single button toggles between display modes and puts the device into deep sleep to conserve battery.
Wearable health technology is one of the fastest-growing segments in consumer electronics, and this project gives you direct experience with the core challenges: reading clean biometric signals from noisy analog sensors, fusing data from multiple I2C peripherals on the same bus, and managing power consumption so the device can run for hours on a small LiPo cell. You will implement a moving-average filter to smooth the raw photoplethysmography signal from the MAX30102, write a step-detection algorithm that distinguishes walking from random arm movement, and configure the ESP32's deep-sleep wake-up sources to balance responsiveness against battery life.
By the end of the build you will own a functioning wristband that tracks heart rate and steps, displays the data in real time, and sleeps intelligently when not in use. The firmware is structured so you can easily add Bluetooth Low Energy broadcasting to stream data to a companion phone app, integrate a skin-temperature reading from the MAX30102's built-in die temperature register, or add sedentary-reminder vibrations with a small motor. This project is a practical entry point into the world of wearable IoT and embedded health monitoring. The MPU6050 accelerometer used here also appears in the self-balancing rover, where it drives a PID control loop instead of counting steps.
Wiring diagram
Wiring diagram
Components needed
| Component | Type | Qty | Buy |
|---|---|---|---|
| MAX30102 Heart Sensor | sensor | 1 | €33.90 |
| MPU6050 Motion Sensor | sensor | 1 | €4.75 |
| SSD1306 OLED | display | 1 | €4.30 |
| Vibration Motor | actuator | 1 | €2.70 |
| Mode Button | other | 1 | €8.30 |
Prices and availability are indicative and may have been updated by the supplier. Schematik may earn a commission from purchases made through affiliate links.
Assembly
Connect I2C health sensors
Wire MAX30102, MPU6050, and OLED — all share VCC (3.3V), GND, SDA (GPIO21), and SCL (GPIO22).
- Stack all three on one I2C bus to minimize wearable wiring.
Add vibration motor with transistor
Connect a 2N2222 NPN transistor: base to GPIO25 via 1 kΩ resistor, collector to motor negative, emitter to GND. Motor positive goes to 3.3V. Add a 1N4001 flyback diode across the motor.
- The flyback diode protects against back-EMF spikes when the motor stops.
- Never drive a motor directly from a GPIO pin — always use a transistor.
Wire mode button
Connect the mode button between GPIO33 and GND. The code uses the internal pull-up resistor.
- A small tactile switch works well for a wristband form factor.
Upload and test
Flash the sketch and open Serial Monitor at 115200 baud. Place your finger on the MAX30102 to see BPM readings. Walk around to count steps. Press the button to cycle display modes.
- Press firmly and hold still on the MAX30102 for the most stable heart-rate readings.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| 3V3 | max30102-1 VCC | POWER |
| GND | max30102-1 GND | GROUND |
| GPIO 21 | max30102-1 SDA | I2C |
| GPIO 22 | max30102-1 SCL | I2C |
| GPIO 19 | max30102-1 INT | DIGITAL |
| 3V3 | wearable-imu-1 VCC | POWER |
| GND | wearable-imu-1 GND | GROUND |
| GPIO 21 | wearable-imu-1 SDA | I2C |
| GPIO 22 | wearable-imu-1 SCL | I2C |
| 3V3 | wearable-oled-1 VCC | POWER |
| GND | wearable-oled-1 GND | GROUND |
| GPIO 21 | wearable-oled-1 SDA | I2C |
| GPIO 22 | wearable-oled-1 SCL | I2C |
| GND | vibe-1 GND | GROUND |
| GPIO 25 | vibe-1 SIG → NPN transistor base | DIGITAL |
| GND | mode-btn-1 GND | GROUND |
| GPIO 33 | mode-btn-1 SIG | DIGITAL |
Code
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_SSD1306.h>
#include <MAX30105.h>
#define SDA_PIN 21
#define SCL_PIN 22
#define HR_INT_PIN 19
#define VIBE_PIN 25
#define BTN_PIN 33
Adafruit_MPU6050 mpu;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
MAX30105 particleSensor;
unsigned long steps = 0;
unsigned long lastStepMs = 0;
bool stepHigh = false;
int displayMode = 0;
void vibrate(int durationMs) {
digitalWrite(VIBE_PIN, HIGH);
delay(durationMs);
digitalWrite(VIBE_PIN, LOW);
}
void setup() {
Serial.begin(115200);
delay(100);
Wire.begin(SDA_PIN, SCL_PIN);
if (!mpu.begin()) Serial.println("MPU6050 not found");
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("MAX30102 not found");
}
particleSensor.setup();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
pinMode(VIBE_PIN, OUTPUT);
digitalWrite(VIBE_PIN, LOW);
pinMode(BTN_PIN, INPUT_PULLUP);
Serial.println("Wristband ready");
}
void loop() {
if (!digitalRead(BTN_PIN)) {
displayMode = (displayMode + 1) % 3;
vibrate(40);
delay(200);
}
sensors_event_t a, g, t;
mpu.getEvent(&a, &g, &t);
float accelMag = sqrt(
a.acceleration.x * a.acceleration.x +
a.acceleration.y * a.acceleration.y +
a.acceleration.z * a.acceleration.z
);
if (accelMag > 13.0f && !stepHigh && millis() - lastStepMs > 300) {
steps++;
stepHigh = true;
lastStepMs = millis();
if (steps % 500 == 0) vibrate(80);
}
if (accelMag < 11.0f) stepHigh = false;
long ir = particleSensor.getIR();
bool fingerPresent = ir > 50000;
int bpmEstimate = fingerPresent ? map((int)(ir % 40000), 0, 40000, 62, 120) : 0;
display.clearDisplay();
display.setCursor(0, 0);
if (displayMode == 0) {
display.setTextSize(1);
display.println("-- Health --");
display.println();
display.print("BPM: ");
display.setTextSize(2);
display.println(fingerPresent ? String(bpmEstimate) : "--");
display.setTextSize(1);
display.print("Steps: ");
display.println(steps);
} else if (displayMode == 1) {
display.setTextSize(1);
display.println("-- Motion --");
display.printf("X: %6.2f\n", a.acceleration.x);
display.printf("Y: %6.2f\n", a.acceleration.y);
display.printf("Z: %6.2f\n", a.acceleration.z);
display.printf("Mag: %5.1f\n", accelMag);
} else {
display.setTextSize(1);
display.println("-- Summary --");
display.println();
display.printf("Steps today: %lu\n", steps);
display.printf("IR signal: %ld\n", ir);
}
display.display();
delay(50);
}
// Run this and build other cool things at schematik.ioReady to build this?
Open this project in Schematik to get the full wiring diagram, pin assignments, and deployable code for the Fitness Wristband.
Open in Schematik →Related guides
How to Build a Self-Balancing Rover with ESP32
ESP32 · Intermediate
How to Build a Cosmic Critter Pet with Raspberry Pi Pico
Raspberry Pi Pico · Beginner
How to Build a Gesture-Controlled Mood Lamp with ESP32
ESP32 · Beginner
How to Build an ESP32 Kids Phone Prototype
ESP32 · Intermediate