Parts list
| Part | Qty | Approx. | Buy |
|---|---|---|---|
| ESP32-C3 SuperMini | 1 | ₹230 | Buy ESP32-C3 SuperMini |
| VL53L1X time-of-flight sensor (with VIN regulator) | 1 | ₹900 | Buy VL53L1X time-of-flight sensor (with VIN regulator) |
| TM1637 4-digit display, 0.56" (or the big 1.2" version) | 1 | ₹90 | Buy TM1637 4-digit display, 0.56" (or the big 1.2" version) |
| Active buzzer module | 1 | ₹25 | Buy Active buzzer module |
| 12 mm push button | 1 | ₹10 | Buy 12 mm push button |
| L-bracket or shelf bracket, 25–30 cm arm | 1 | ₹80 | Buy L-bracket or shelf bracket, 25–30 cm arm |
| 5 V USB adapter + cable (2 m) | 1 | ₹180 | Buy 5 V USB adapter + cable (2 m) |
| Total | ₹1,515 |
Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.
Wiring
| ESP32-C3 SuperMini | Part · pin | Wire | Notes |
|---|---|---|---|
| 6 | VL53L1X time-of-flight distance sensor (up to 4 m) · SDA | blue | GPIO6 — I2C kept off strapping pins 8 and 9 |
| 7 | VL53L1X time-of-flight distance sensor (up to 4 m) · SCL | yellow | GPIO7 |
| 3V3 | VL53L1X time-of-flight distance sensor (up to 4 m) · VIN | red | |
| GND | VL53L1X time-of-flight distance sensor (up to 4 m) · GND | black | GPIO1 and XSHUT on the sensor stay unconnected |
| 3 | TM1637 4-digit 7-segment display · CLK | yellow | |
| 4 | TM1637 4-digit 7-segment display · DIO | green | |
| 3V3 | TM1637 4-digit 7-segment display · VCC | red | Runs from 3.3 V (slightly dimmer than at 5 V); keeps CLK/DIO at 3.3 V |
| GND | TM1637 4-digit 7-segment display · GND | black | |
| 10 | Push button (6 mm tactile switch) · 1 | green | GPIO10 — press with nobody underneath to measure the floor |
| GND | Push button (6 mm tactile switch) · 2 | black | |
| 1 | Active buzzer module (high-level trigger) · I/O | orange | GPIO1 — a short beep when the reading is steady |
| 3V3 | Active buzzer module (high-level trigger) · VCC | red | |
| GND | Active buzzer module (high-level trigger) · GND | black |
Board quirks: Mount the sensor about 2.2 m above the floor on a bracket that sticks out from the wall, so it looks straight down at a clear patch of floor. A tilted sensor measures a slanted (longer) path. The sensor measures to the top of the hair, not the scalp. Ask people to stand straight under it with heels together, like with a tape — expect ±1 cm, a little more for thick hair or a cap. The VL53L1X’s infrared is weakened by strong sunlight. Indoors, long-distance mode reaches past 3 m; outdoors in daylight it may not see the floor at all. Calibration (the floor distance) is saved in flash, so it only needs repeating if the bracket moves. The display shows <code>CAL</code> until it has been done. I2C uses GPIO6/7 with <code>Wire.begin(6, 7)</code> because the SuperMini’s default I2C pins are strapping pins with the LED and BOOT button on them.
Code
// Height measuring station — ESP32-C3 SuperMini + VL53L1X (pointing down) + TM1637 display.
// Mount the sensor on a bracket above head height, looking straight down at the floor.
// Height = (distance to the floor) - (distance to the top of the head). Press the button
// once with nobody underneath to measure the floor; it's saved in flash.
// The reading is the median of 15 measurements, shown when it is steady; a beep confirms.
// VL53L1X: SDA 6, SCL 7 (keeps I2C off strapping pins 8/9). TM1637: CLK 3, DIO 4.
// Button: GPIO10 to GND. Active buzzer I/O: GPIO1. Board option: USB CDC On Boot.
#include <Wire.h>
#include <VL53L1X.h>
#include <TM1637Display.h>
#include <Preferences.h>
#include <utility>
constexpr int SDA_PIN = 6, SCL_PIN = 7, TM_CLK = 3, TM_DIO = 4, BTN = 10, BUZZER = 1;
constexpr int MIN_CM = 60; // ignore anything shorter: a hand, a bag
VL53L1X tof;
TM1637Display disp(TM_CLK, TM_DIO);
Preferences prefs;
int floorMm = 0;
const uint8_t SEG_CAL[] = {SEG_A | SEG_D | SEG_E | SEG_F, SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G, SEG_D | SEG_E | SEG_F, 0}; // "CAL"
const uint8_t SEG_DASH[] = {SEG_G, SEG_G, SEG_G, SEG_G};
int medianMm(int n) {
int v[25], k = 0;
for (int i = 0; i < n && i < 25; i++) {
tof.read();
if (!tof.timeoutOccurred() && tof.ranging_data.range_status == VL53L1X::RangeValid) v[k++] = tof.ranging_data.range_mm;
}
if (k < n / 2) return -1;
for (int i = 1; i < k; i++) // insertion sort, k <= 25
for (int j = i; j > 0 && v[j] < v[j - 1]; j--) std::swap(v[j], v[j - 1]);
return v[k / 2];
}
void beep(int ms) {
digitalWrite(BUZZER, HIGH);
delay(ms);
digitalWrite(BUZZER, LOW);
}
void setup() {
Serial.begin(115200);
pinMode(BTN, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
disp.setBrightness(5);
disp.setSegments(SEG_DASH);
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(400000);
tof.setTimeout(500);
if (!tof.init()) {
Serial.println("VL53L1X not found at 0x29 - check SDA 6 / SCL 7");
disp.setSegments(SEG_CAL);
while (true) delay(1000);
}
tof.setDistanceMode(VL53L1X::Long); // up to ~4 m in the dark, ~3 m indoors
tof.setMeasurementTimingBudget(100000);
tof.startContinuous(100);
prefs.begin("height", false);
floorMm = prefs.getInt("floor", 0);
Serial.printf("Floor at %d mm%s\n", floorMm, floorMm ? "" : " - press the button with nobody underneath");
}
void loop() {
if (digitalRead(BTN) == LOW) {
disp.setSegments(SEG_CAL);
int f = medianMm(20);
if (f > 1000) {
floorMm = f;
prefs.putInt("floor", floorMm);
Serial.printf("Floor calibrated: %d mm\n", floorMm);
beep(300);
} else Serial.println("Calibration failed - is the sensor at least 1 m above the floor?");
while (digitalRead(BTN) == LOW) delay(10);
}
if (!floorMm) {
disp.setSegments(SEG_CAL);
delay(200);
return;
}
static int lastCm = 0, steady = 0;
int head = medianMm(15);
int cm = head > 0 ? (floorMm - head + 5) / 10 : 0;
if (cm < MIN_CM) { // nobody there
disp.setSegments(SEG_DASH);
steady = 0;
lastCm = 0;
return;
}
steady = abs(cm - lastCm) <= 1 ? steady + 1 : 0;
lastCm = cm;
disp.showNumberDec(cm);
if (steady == 3) { // same height three times in a row: that's the reading
Serial.printf("Height: %d cm\n", cm);
beep(80);
}
}// Height measuring station — ESP32-C3 SuperMini + VL53L1X (pointing down) + TM1637 display.
// Mount the sensor on a bracket above head height, looking straight down at the floor.
// Height = (distance to the floor) - (distance to the top of the head). Press the button
// once with nobody underneath to measure the floor; it's saved in flash.
// The reading is the median of 15 measurements, shown when it is steady; a beep confirms.
// VL53L1X: SDA 6, SCL 7 (keeps I2C off strapping pins 8/9). TM1637: CLK 3, DIO 4.
// Button: GPIO10 to GND. Active buzzer I/O: GPIO1. Board option: USB CDC On Boot.
#include <Wire.h>
#include <VL53L1X.h>
#include <TM1637Display.h>
#include <Preferences.h>
#include <utility>
constexpr int SDA_PIN = 6, SCL_PIN = 7, TM_CLK = 3, TM_DIO = 4, BTN = 10, BUZZER = 1;
constexpr int MIN_CM = 60; // ignore anything shorter: a hand, a bag
VL53L1X tof;
TM1637Display disp(TM_CLK, TM_DIO);
Preferences prefs;
int floorMm = 0;
const uint8_t SEG_CAL[] = {SEG_A | SEG_D | SEG_E | SEG_F, SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G, SEG_D | SEG_E | SEG_F, 0}; // "CAL"
const uint8_t SEG_DASH[] = {SEG_G, SEG_G, SEG_G, SEG_G};
int medianMm(int n) {
int v[25], k = 0;
for (int i = 0; i < n && i < 25; i++) {
tof.read();
if (!tof.timeoutOccurred() && tof.ranging_data.range_status == VL53L1X::RangeValid) v[k++] = tof.ranging_data.range_mm;
}
if (k < n / 2) return -1;
for (int i = 1; i < k; i++) // insertion sort, k <= 25
for (int j = i; j > 0 && v[j] < v[j - 1]; j--) std::swap(v[j], v[j - 1]);
return v[k / 2];
}
void beep(int ms) {
digitalWrite(BUZZER, HIGH);
delay(ms);
digitalWrite(BUZZER, LOW);
}
void setup() {
Serial.begin(115200);
pinMode(BTN, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
disp.setBrightness(5);
disp.setSegments(SEG_DASH);
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(400000);
tof.setTimeout(500);
if (!tof.init()) {
Serial.println("VL53L1X not found at 0x29 - check SDA 6 / SCL 7");
disp.setSegments(SEG_CAL);
while (true) delay(1000);
}
tof.setDistanceMode(VL53L1X::Long); // up to ~4 m in the dark, ~3 m indoors
tof.setMeasurementTimingBudget(100000);
tof.startContinuous(100);
prefs.begin("height", false);
floorMm = prefs.getInt("floor", 0);
Serial.printf("Floor at %d mm%s\n", floorMm, floorMm ? "" : " - press the button with nobody underneath");
}
void loop() {
if (digitalRead(BTN) == LOW) {
disp.setSegments(SEG_CAL);
int f = medianMm(20);
if (f > 1000) {
floorMm = f;
prefs.putInt("floor", floorMm);
Serial.printf("Floor calibrated: %d mm\n", floorMm);
beep(300);
} else Serial.println("Calibration failed - is the sensor at least 1 m above the floor?");
while (digitalRead(BTN) == LOW) delay(10);
}
if (!floorMm) {
disp.setSegments(SEG_CAL);
delay(200);
return;
}
static int lastCm = 0, steady = 0;
int head = medianMm(15);
int cm = head > 0 ? (floorMm - head + 5) / 10 : 0;
if (cm < MIN_CM) { // nobody there
disp.setSegments(SEG_DASH);
steady = 0;
lastCm = 0;
return;
}
steady = abs(cm - lastCm) <= 1 ? steady + 1 : 0;
lastCm = cm;
disp.showNumberDec(cm);
if (steady == 3) { // same height three times in a row: that's the reading
Serial.printf("Height: %d cm\n", cm);
beep(80);
}
}Compiled for c3supermini on esp32 3.3.12 with VL53L1X 1.3.1 · 3 Nov 2026
How it works
School health camps and anganwadi checkups measure a lot of children, usually with a tape on the wall and a ruler balanced on each head. This station measures without touching: a laser rangefinder on a bracket above head height looks straight down, and the height appears on a large display with a beep.
The VL53L1X is a time-of-flight sensor. It sends out a tiny infrared laser pulse and times the reflection, up to about 4 m. First, with nobody underneath, you press the button: the C3 takes the median of 20 readings as the floor distance and stores it in flash. After that, when someone stands underneath, the sensor measures the distance to the top of their head, and height = floor distance − head distance.
Each reading is the median of 15 measurements, which throws away the odd reflection from a shoulder or a moving hand. Anything shorter than 60 cm is ignored. When three readings in a row agree within 1 cm, the buzzer beeps and the Serial Monitor prints the height. The TM1637 four-digit display shows it in centimetres, and dashes when nobody is there.
The ESP32-C3 SuperMini is plenty for this and costs very little. It runs everything from one USB adapter, and I2C on GPIO6/7 keeps the sensor away from the strapping pins.
Troubleshooting
It shows CAL and never a height
Press the button with nobody underneath. If the Serial Monitor says calibration failed, the sensor didn’t see anything beyond 1 m — check it points at the floor and isn’t blocked by the bracket.
Heights are a few cm off
Re-calibrate, then check with a tape on one person. The usual cause is a sensor that isn’t level; a shim under the bracket fixes it.
The display flickers between two numbers
The person is moving, or the sensor sees the edge of their head. The beep only sounds after three steady readings — wait for it.
Upgrades
- Add a load cell platform (HX711) and show BMI next to the height.
- Send each reading with a student ID to a Google Sheet over Wi-Fi.
- Use the bigger 1.2″ TM1637 module so a whole queue can read it.



